diff options
author | kostja@vajra.(none) <> | 2007-04-05 15:49:46 +0400 |
---|---|---|
committer | kostja@vajra.(none) <> | 2007-04-05 15:49:46 +0400 |
commit | 701ed297d0f7c2f4c769878a9def7c2945ea3055 (patch) | |
tree | a91b4f984fd6317085b8bd7e94cd91134184aab6 /sql/events.cc | |
parent | fe6835c2b93ab6767553b427fd9259d88de67e8e (diff) | |
parent | 98db2300865004a4bb573caebc82b25fb5b08911 (diff) | |
download | mariadb-git-701ed297d0f7c2f4c769878a9def7c2945ea3055.tar.gz |
Merge bk-internal.mysql.com:/home/bk/mysql-5.1-runtime
into vajra.(none):/opt/local/work/mysql-5.1-c1
Diffstat (limited to 'sql/events.cc')
-rw-r--r-- | sql/events.cc | 956 |
1 files changed, 530 insertions, 426 deletions
diff --git a/sql/events.cc b/sql/events.cc index 46111bcaa91..7c44116af55 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -19,7 +19,6 @@ #include "event_db_repository.h" #include "event_queue.h" #include "event_scheduler.h" -#include "sp_head.h" /* TODO list : @@ -66,7 +65,7 @@ static const char *opt_event_scheduler_state_names[]= { "OFF", "ON", "0", "1", "DISABLED", NullS }; -TYPELIB Events::opt_typelib= +const TYPELIB Events::opt_typelib= { array_elements(opt_event_scheduler_state_names)-1, "", @@ -82,7 +81,7 @@ TYPELIB Events::opt_typelib= */ static const char *var_event_scheduler_state_names[]= { "OFF", "ON", NullS }; -TYPELIB Events::var_typelib= +const TYPELIB Events::var_typelib= { array_elements(var_event_scheduler_state_names)-1, "", @@ -90,20 +89,13 @@ TYPELIB Events::var_typelib= NULL }; - -static -Event_queue events_event_queue; - -static -Event_scheduler events_event_scheduler; - - -Event_db_repository events_event_db_repository; - -Events Events::singleton; - -enum Events::enum_opt_event_scheduler Events::opt_event_scheduler= - Events::EVENTS_OFF; +Event_queue *Events::event_queue; +Event_scheduler *Events::scheduler; +Event_db_repository *Events::db_repository; +enum Events::enum_opt_event_scheduler +Events::opt_event_scheduler= Events::EVENTS_OFF; +pthread_mutex_t Events::LOCK_event_metadata; +bool Events::check_system_tables_error= FALSE; /* @@ -128,25 +120,89 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) } -/* - Accessor for the singleton instance. +/** + @brief Initialize the start up option of the Events scheduler. - SYNOPSIS - Events::get_instance() + Do not initialize the scheduler subsystem yet - the initialization + is split into steps as it has to fit into the common MySQL + initialization framework. + No locking as this is called only at start up. - RETURN VALUE - address + @param[in,out] argument The value of the argument. If this value + is found in the typelib, the argument is + updated. + + @retval TRUE unknown option value + @retval FALSE success */ -Events * -Events::get_instance() +bool +Events::set_opt_event_scheduler(char *argument) { - DBUG_ENTER("Events::get_instance"); - DBUG_RETURN(&singleton); + if (argument == NULL) + opt_event_scheduler= Events::EVENTS_DISABLED; + else + { + int type; + /* + type= 1 2 3 4 5 + (OFF | ON) - (0 | 1) (DISABLE ) + */ + const static enum enum_opt_event_scheduler type2state[]= + { EVENTS_OFF, EVENTS_ON, EVENTS_OFF, EVENTS_ON, EVENTS_DISABLED }; + + type= find_type(argument, &opt_typelib, 1); + + DBUG_ASSERT(type >= 0 && type <= 5); /* guaranteed by find_type */ + + if (type == 0) + { + fprintf(stderr, "Unknown option to event-scheduler: %s\n", argument); + return TRUE; + } + opt_event_scheduler= type2state[type-1]; + } + return FALSE; } -/* +/** + Return a string representation of the current scheduler mode. +*/ + +const char * +Events::get_opt_event_scheduler_str() +{ + const char *str; + + pthread_mutex_lock(&LOCK_event_metadata); + str= opt_typelib.type_names[(int) opt_event_scheduler]; + pthread_mutex_unlock(&LOCK_event_metadata); + + return str; +} + + +/** + Push an error into the error stack if the system tables are + not up to date. +*/ + +bool Events::check_if_system_tables_error() +{ + DBUG_ENTER("Events::check_if_system_tables_error"); + + if (check_system_tables_error) + { + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(FALSE); +} + + +/** Reconstructs interval expression from interval type and expression value that is in form of a value of the smalles entity: For @@ -278,52 +334,65 @@ 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() +/** + Create a new event. + + @param[in,out] thd THD + @param[in] parse_data Event's data from parsing stage + @param[in] if_not_exists Whether IF NOT EXISTS was + specified + In case there is an event with the same name (db) and + IF NOT EXISTS is specified, an warning is put into the stack. + @sa Events::drop_event for the notes about locking, pre-locking + and Events DDL. + + @retval FALSE OK + @retval TRUE Error (reported) */ -Events::Events() +bool +Events::create_event(THD *thd, Event_parse_data *parse_data, + bool if_not_exists) { - scheduler= &events_event_scheduler; - event_queue= &events_event_queue; - db_repository= &events_event_db_repository; -} + int ret; + DBUG_ENTER("Events::create_event"); + /* + Let's commit the transaction first - MySQL manual specifies + that a DDL issues an implicit commit, and it doesn't say "successful + DDL", so that an implicit commit is a property of any successfully + parsed DDL statement. + */ + if (end_active_trans(thd)) + DBUG_RETURN(TRUE); -/* - The function exported to the world for creating of events. + if (check_if_system_tables_error()) + DBUG_RETURN(TRUE); - SYNOPSIS - 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 + /* + Perform semantic checks outside of Event_db_repository: + once CREATE EVENT is supported in prepared statements, the + checks will be moved to PREPARE phase. + */ + if (parse_data->check_parse_data(thd)) + DBUG_RETURN(TRUE); - RETURN VALUE - FALSE OK - TRUE Error (Reported) + /* At create, one of them must be set */ + DBUG_ASSERT(parse_data->expression || parse_data->execute_at); - 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. -*/ + if (check_access(thd, EVENT_ACL, parse_data->dbname.str, 0, 0, 0, + is_schema_db(parse_data->dbname.str))) + DBUG_RETURN(TRUE); -bool -Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists) -{ - int ret; - DBUG_ENTER("Events::create_event"); - if (unlikely(check_system_tables_error)) + if (check_db_dir_existence(parse_data->dbname.str)) { - my_error(ER_EVENTS_DB_ERROR, MYF(0)); + my_error(ER_BAD_DB_ERROR, MYF(0)); DBUG_RETURN(TRUE); } + if (parse_data->do_not_create) + DBUG_RETURN(FALSE); /* Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for CREATE EVENT command. @@ -334,8 +403,7 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists) 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)) && - !parse_data->do_not_create) + if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists))) { Event_queue_element *new_element; @@ -345,12 +413,17 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists) parse_data->name, new_element))) { - DBUG_ASSERT(ret == OP_LOAD_ERROR); + db_repository->drop_event(thd, parse_data->dbname, parse_data->name, + TRUE); delete new_element; } - else /* Binlog the create event. */ + else { - event_queue->create_event(thd, new_element); + /* TODO: do not ignore the out parameter and a possible OOM error! */ + bool created; + if (event_queue) + event_queue->create_event(thd, new_element, &created); + /* Binlog the create event. */ if (mysql_bin_log.is_open() && (thd->query_length > 0)) { thd->clear_error(); @@ -365,37 +438,79 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists) } -/* - The function exported to the world for alteration of events. - - SYNOPSIS - 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 - FALSE OK - TRUE 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 +/** + Alter an event. + + @param[in,out] thd THD + @param[in] parse_data Event's data from parsing stage + @param[in] new_dbname A new schema name for the event. Set in the case of + ALTER EVENT RENAME, otherwise is NULL. + @param[in] new_name A new name for the event. Set in the case of + ALTER EVENT RENAME + + Parameter 'et' contains data about dbname and event name. + Parameter 'new_name' is the new name of the event, if not null + this means that RENAME TO was specified in the query + @sa Events::drop_event for the locking notes. + + @retval FALSE OK + @retval TRUE error (reported) */ bool -Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) +Events::update_event(THD *thd, Event_parse_data *parse_data, + LEX_STRING *new_dbname, LEX_STRING *new_name) { int ret; Event_queue_element *new_element; + 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)) - { - my_error(ER_EVENTS_DB_ERROR, MYF(0)); + + /* + For consistency, implicit COMMIT should be the first thing in the + execution chain. + */ + if (end_active_trans(thd)) + DBUG_RETURN(TRUE); + + if (check_if_system_tables_error()) DBUG_RETURN(TRUE); + + if (parse_data->check_parse_data(thd) || parse_data->do_not_create) + DBUG_RETURN(TRUE); + + if (check_access(thd, EVENT_ACL, parse_data->dbname.str, 0, 0, 0, + is_schema_db(parse_data->dbname.str))) + DBUG_RETURN(TRUE); + + if (new_dbname) /* It's a rename */ + { + /* Check that the new and the old names differ. */ + if ( !sortcmp_lex_string(parse_data->dbname, *new_dbname, + system_charset_info) && + !sortcmp_lex_string(parse_data->name, *new_name, + system_charset_info)) + { + my_error(ER_EVENT_SAME_NAME, MYF(0), parse_data->name.str); + DBUG_RETURN(TRUE); + } + + /* + And the user has sufficient privileges to use the target database. + Do it before checking whether the database exists: we don't want + to tell the user that a database doesn't exist if they can not + access it. + */ + if (check_access(thd, EVENT_ACL, new_dbname->str, 0, 0, 0, + is_schema_db(new_dbname->str))) + DBUG_RETURN(TRUE); + + /* Check that the target database exists */ + if (check_db_dir_existence(new_dbname->str)) + { + my_error(ER_BAD_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } } /* @@ -408,7 +523,8 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) 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))) + if (!(ret= db_repository->update_event(thd, parse_data, + new_dbname, new_name))) { LEX_STRING dbname= new_dbname ? *new_dbname : parse_data->dbname; LEX_STRING name= new_name ? *new_name : parse_data->name; @@ -421,10 +537,18 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) DBUG_ASSERT(ret == OP_LOAD_ERROR); delete new_element; } - else /* Binlog the alter event. */ + else { - event_queue->update_event(thd, parse_data->dbname, parse_data->name, - new_element); + /* + TODO: check if an update actually has inserted an entry + into the queue. + If not, and the element is ON COMPLETION NOT PRESERVE, delete + it right away. + */ + if (event_queue) + event_queue->update_event(thd, parse_data->dbname, parse_data->name, + new_element); + /* Binlog the alter event. */ if (mysql_bin_log.is_open() && (thd->query_length > 0)) { thd->clear_error(); @@ -439,20 +563,28 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) } -/* +/** Drops an event - SYNOPSIS - 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 - - RETURN VALUE - FALSE OK - TRUE Error (reported) + @param[in,out] thd THD + @param[in] dbname Event's schema + @param[in] name Event's name + @param[in] if_exists When this is set and the event does not exist + a warning is pushed into the warning stack. + Otherwise the operation produces an error. + + @note Similarly to DROP PROCEDURE, we do not allow DROP EVENT + under LOCK TABLES mode, unless table mysql.event is locked. To + ensure that, we do not reset & backup the open tables state in + this function - if in LOCK TABLES or pre-locking mode, this will + lead to an error 'Table mysql.event is not locked with LOCK + TABLES' unless it _is_ locked. In pre-locked mode there is + another barrier - DROP EVENT commits the current transaction, + and COMMIT/ROLLBACK is not allowed in stored functions and + triggers. + + @retval FALSE OK + @retval TRUE Error (reported) */ bool @@ -460,14 +592,30 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) { int ret; DBUG_ENTER("Events::drop_event"); - if (unlikely(check_system_tables_error)) - { - my_error(ER_EVENTS_DB_ERROR, MYF(0)); + + /* + In MySQL, DDL must always commit: since mysql.* tables are + non-transactional, we must modify them outside a transaction + to not break atomicity. + But the second and more important reason to commit here + regardless whether we're actually changing mysql.event table + or not is replication: end_active_trans syncs the binary log, + and unless we run DDL in it's own transaction it may simply + never appear on the slave in case the outside transaction + rolls back. + */ + if (end_active_trans(thd)) DBUG_RETURN(TRUE); - } - /* - Turn off row binlogging of this statement and use statement-based so + if (check_if_system_tables_error()) + DBUG_RETURN(TRUE); + + if (check_access(thd, EVENT_ACL, dbname.str, 0, 0, 0, + is_schema_db(dbname.str))) + DBUG_RETURN(TRUE); + + /* + Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for DROP EVENT command. */ if (thd->current_stmt_binlog_row_based) @@ -477,7 +625,8 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists))) { - event_queue->drop_event(thd, dbname, name); + if (event_queue) + event_queue->drop_event(thd, dbname, name); /* Binlog the drop event. */ if (mysql_bin_log.is_open() && (thd->query_length > 0)) { @@ -494,10 +643,12 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) /** Drops all events from a schema - SYNOPSIS - Events::drop_schema_events() - thd Thread - db ASCIIZ schema name + @note We allow to drop all events in a schema even if the + scheduler is disabled. This is to not produce any warnings + in case of DROP DATABASE and a disabled scheduler. + + @param[in,out] thd Thread + @param[in] db ASCIIZ schema name */ void @@ -507,14 +658,15 @@ Events::drop_schema_events(THD *thd, char *db) DBUG_ENTER("Events::drop_schema_events"); DBUG_PRINT("enter", ("dropping events from %s", db)); - if (unlikely(check_system_tables_error)) - { - my_error(ER_EVENTS_DB_ERROR, MYF(0)); - DBUG_VOID_RETURN; - } + + /* + sic: no check if the scheduler is disabled or system tables + are damaged, as intended. + */ pthread_mutex_lock(&LOCK_event_metadata); - event_queue->drop_schema_events(thd, db_lex); + if (event_queue) + event_queue->drop_schema_events(thd, db_lex); db_repository->drop_schema_events(thd, db_lex); pthread_mutex_unlock(&LOCK_event_metadata); @@ -522,115 +674,137 @@ Events::drop_schema_events(THD *thd, char *db) } -/* - SHOW CREATE EVENT - - SYNOPSIS - Events::show_create_event() - thd Thread context - spn The name of the event (db, name) - - RETURN VALUE - FALSE OK - TRUE Error during writing to the wire +/** + A helper function to generate SHOW CREATE EVENT output from + a named event */ -bool -Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) +static bool +send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol) { - CHARSET_INFO *scs= system_charset_info; - int ret; - Event_timed *et= new Event_timed(); + char show_str_buf[10 * STRING_BUFFER_USUAL_SIZE]; + String show_str(show_str_buf, sizeof(show_str_buf), system_charset_info); + List<Item> field_list; + LEX_STRING sql_mode; + const String *tz_name; - 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_EVENTS_DB_ERROR, MYF(0)); + DBUG_ENTER("send_show_create_event"); + + show_str.length(0); + if (et->get_create_event(thd, &show_str)) DBUG_RETURN(TRUE); - } - ret= db_repository->load_named_event(thd, dbname, name, et); + field_list.push_back(new Item_empty_string("Event", NAME_LEN)); - if (!ret) - { - 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; + if (sys_var_thd_sql_mode::symbolic_mode_representation(thd, et->sql_mode, + &sql_mode)) + DBUG_RETURN(TRUE); - show_str.length(0); - show_str.set_charset(system_charset_info); + field_list.push_back(new Item_empty_string("sql_mode", sql_mode.length)); - if (et->get_create_event(thd, &show_str)) - goto err; + tz_name= et->time_zone->get_name(); - field_list.push_back(new Item_empty_string("Event", NAME_LEN)); + field_list.push_back(new Item_empty_string("time_zone", + tz_name->length())); - 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("Create Event", + show_str.length())); - field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len)); + if (protocol->send_fields(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); - const String *tz_name= et->time_zone->get_name(); - field_list.push_back(new Item_empty_string("time_zone", - tz_name->length())); + protocol->prepare_for_resend(); - field_list.push_back(new Item_empty_string("Create Event", - show_str.length())); + protocol->store(et->name.str, et->name.length, system_charset_info); + protocol->store(sql_mode.str, sql_mode.length, system_charset_info); + protocol->store(tz_name->ptr(), tz_name->length(), system_charset_info); + protocol->store(show_str.c_ptr(), show_str.length(), system_charset_info); - if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | - Protocol::SEND_EOF)) - goto err; + if (protocol->write()) + DBUG_RETURN(TRUE); - protocol->prepare_for_resend(); - protocol->store(et->name.str, et->name.length, scs); + send_eof(thd); - protocol->store((char*) sql_mode_str, sql_mode_len, scs); + DBUG_RETURN(FALSE); +} - protocol->store((char*) tz_name->ptr(), tz_name->length(), scs); - protocol->store(show_str.c_ptr(), show_str.length(), scs); - ret= protocol->write(); - send_eof(thd); - } - delete et; +/** + Implement SHOW CREATE EVENT statement + + thd Thread context + spn The name of the event (db, name) + + @retval FALSE OK + @retval TRUE error (reported) +*/ + +bool +Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) +{ + Open_tables_state open_tables_backup; + Event_timed et; + bool ret; + + DBUG_ENTER("Events::show_create_event"); + DBUG_PRINT("enter", ("name: %s@%s", dbname.str, name.str)); + + if (check_if_system_tables_error()) + DBUG_RETURN(TRUE); + + if (check_access(thd, EVENT_ACL, dbname.str, 0, 0, 0, + is_schema_db(dbname.str))) + DBUG_RETURN(TRUE); + + /* + We would like to allow SHOW CREATE EVENT under LOCK TABLES and + in pre-locked mode. mysql.event table is marked as a system table. + This flag reduces the set of its participation scenarios in LOCK TABLES + operation, and therefore an out-of-bound open of this table + for reading like the one below (sic, only for reading) is + more or less deadlock-free. For additional information about when a + deadlock can occur please refer to the description of 'system table' + flag. + */ + thd->reset_n_backup_open_tables_state(&open_tables_backup); + ret= db_repository->load_named_event(thd, dbname, name, &et); + thd->restore_backup_open_tables_state(&open_tables_backup); + + if (!ret) + ret= send_show_create_event(thd, &et, thd->protocol); + DBUG_RETURN(ret); -err: - delete et; - DBUG_RETURN(TRUE); } -/* - Proxy for Event_db_repository::fill_schema_events. - Callback for I_S from sql_show.cc +/** + Check access rights and fill INFORMATION_SCHEMA.events table. - SYNOPSIS - Events::fill_schema_events() - thd Thread context - tables The schema table + @param[in,out] thd Thread context + @param[in] table The temporary table to fill. cond Unused - RETURN VALUE - 0 OK - !0 Error + In MySQL INFORMATION_SCHEMA tables are temporary tables that are + created and filled on demand. In this function, we fill + INFORMATION_SCHEMA.events. It is a callback for I_S module, invoked from + sql_show.cc + + @return Has to be integer, as such is the requirement of the I_S API + @retval 0 success + @retval 1 an error, pushed into the error stack */ int Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) { char *db= NULL; + int ret; + Open_tables_state open_tables_backup; 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); - } + + if (check_if_system_tables_error()) + DBUG_RETURN(1); /* If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to @@ -644,7 +818,17 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) DBUG_RETURN(1); db= thd->lex->select_lex.db; } - DBUG_RETURN(myself->db_repository->fill_schema_events(thd, tables, db)); + /* + Reset and backup of the currently open tables in this thread + is a way to allow SELECTs from INFORMATION_SCHEMA.events under + LOCK TABLES and in pre-locked mode. See also + Events::show_create_event for additional comments. + */ + thd->reset_n_backup_open_tables_state(&open_tables_backup); + ret= db_repository->fill_schema_events(thd, tables, db); + thd->restore_backup_open_tables_state(&open_tables_backup); + + DBUG_RETURN(ret); } @@ -663,14 +847,17 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) */ bool -Events::init() +Events::init(my_bool opt_noacl) { THD *thd; bool res= FALSE; + DBUG_ENTER("Events::init"); - if (opt_event_scheduler == Events::EVENTS_DISABLED) - DBUG_RETURN(FALSE); + /* Disable the scheduler if running with --skip-grant-tables */ + if (opt_noacl) + opt_event_scheduler= EVENTS_DISABLED; + /* We need a temporary THD during boot */ if (!(thd= new THD())) @@ -686,30 +873,67 @@ Events::init() thd->thread_stack= (char*) &thd; thd->store_globals(); - if (check_system_tables(thd)) + /* + We will need Event_db_repository anyway, even if the scheduler is + disabled - to perform events DDL. + */ + if (!(db_repository= new Event_db_repository)) { - check_system_tables_error= TRUE; - sql_print_error("Event Scheduler: The system tables are damaged. " - "The scheduler subsystem will be unusable during this run."); + res= TRUE; /* fatal error: request unireg_abort */ goto end; } - check_system_tables_error= FALSE; - if (event_queue->init_queue(thd) || load_events_from_db(thd)) + /* + Since we allow event DDL even if the scheduler is disabled, + check the system tables, as we might need them. + */ + if (Event_db_repository::check_system_tables(thd)) { - sql_print_error("Event Scheduler: Error while loading from disk."); + sql_print_error("Event Scheduler: An error occurred when initializing " + "system tables.%s", + opt_event_scheduler == EVENTS_DISABLED ? + "" : " Disabling the Event Scheduler."); + + /* Disable the scheduler since the system tables are not up to date */ + opt_event_scheduler= EVENTS_DISABLED; + check_system_tables_error= TRUE; goto end; } - scheduler->init_scheduler(event_queue); + /* + Was disabled explicitly from the command line, or because we're running + with --skip-grant-tables, or because we have no system tables. + */ + if (opt_event_scheduler == Events::EVENTS_DISABLED) + goto end; + DBUG_ASSERT(opt_event_scheduler == Events::EVENTS_ON || opt_event_scheduler == Events::EVENTS_OFF); - if (opt_event_scheduler == Events::EVENTS_ON) - res= scheduler->start(); - Event_worker_thread::init(this, db_repository); + if (!(event_queue= new Event_queue) || + !(scheduler= new Event_scheduler(event_queue))) + { + res= TRUE; /* fatal error: request unireg_abort */ + goto end; + } + + if (event_queue->init_queue(thd) || load_events_from_db(thd) || + opt_event_scheduler == EVENTS_ON && scheduler->start()) + { + sql_print_error("Event Scheduler: Error while loading from disk."); + res= TRUE; /* fatal error: request unireg_abort */ + goto end; + } + Event_worker_thread::init(db_repository); + end: + if (res) + { + delete db_repository; + delete event_queue; + delete scheduler; + } delete thd; /* Remember that we don't have a THD */ my_pthread_setspecific_ptr(THR_THD, NULL); @@ -732,14 +956,18 @@ void Events::deinit() { DBUG_ENTER("Events::deinit"); - if (likely(!check_system_tables_error)) - { - scheduler->stop(); - scheduler->deinit_scheduler(); - event_queue->deinit_queue(); + if (opt_event_scheduler != EVENTS_DISABLED) + { + delete scheduler; + scheduler= NULL; /* safety */ + delete event_queue; + event_queue= NULL; /* safety */ } + delete db_repository; + db_repository= NULL; /* safety */ + DBUG_VOID_RETURN; } @@ -756,8 +984,6 @@ void Events::init_mutexes() { pthread_mutex_init(&LOCK_event_metadata, MY_MUTEX_INIT_FAST); - event_queue->init_mutexes(); - scheduler->init_mutexes(); } @@ -771,8 +997,6 @@ Events::init_mutexes() void Events::destroy_mutexes() { - event_queue->deinit_mutexes(); - scheduler->deinit_mutexes(); pthread_mutex_destroy(&LOCK_event_metadata); } @@ -795,283 +1019,163 @@ Events::dump_internal_status() puts("LLA = Last Locked At LUA = Last Unlocked At"); puts("WOC = Waiting On Condition DL = Data Locked"); - scheduler->dump_internal_status(); - event_queue->dump_internal_status(); + pthread_mutex_lock(&LOCK_event_metadata); + if (opt_event_scheduler == EVENTS_DISABLED) + puts("The Event Scheduler is disabled"); + else + { + scheduler->dump_internal_status(); + event_queue->dump_internal_status(); + } + pthread_mutex_unlock(&LOCK_event_metadata); DBUG_VOID_RETURN; } /** - Starts execution of events by the scheduler + Starts or stops the event scheduler thread. - SYNOPSIS - Events::start_execution_of_events() - - RETURN VALUE - FALSE OK - TRUE Error + @retval FALSE success + @retval TRUE error */ bool -Events::start_execution_of_events() +Events::start_or_stop_event_scheduler(enum_opt_event_scheduler start_or_stop) { - DBUG_ENTER("Events::start_execution_of_events"); - if (unlikely(check_system_tables_error)) - { - my_error(ER_EVENTS_DB_ERROR, MYF(0)); - DBUG_RETURN(TRUE); - } - DBUG_RETURN(scheduler->start()); -} + bool ret= FALSE; + DBUG_ENTER("Events::start_or_stop_event_scheduler"); -/* - Stops execution of events by the scheduler. - Already running events will not be stopped. If the user needs - them stopped manual intervention is needed. + DBUG_ASSERT(start_or_stop == Events::EVENTS_ON || + start_or_stop == Events::EVENTS_OFF); - SYNOPSIS - Events::stop_execution_of_events() - - RETURN VALUE - FALSE OK - TRUE Error -*/ - -bool -Events::stop_execution_of_events() -{ - DBUG_ENTER("Events::stop_execution_of_events"); - if (unlikely(check_system_tables_error)) - { - my_error(ER_EVENTS_DB_ERROR, MYF(0)); + /* + If the scheduler was disabled because there are no/bad + system tables, produce a more meaningful error message + than ER_OPTION_PREVENTS_STATEMENT + */ + if (check_if_system_tables_error()) DBUG_RETURN(TRUE); - } - DBUG_RETURN(scheduler->stop()); -} - - -/* - Checks whether the scheduler is running or not. - - SYNOPSIS - Events::is_started() - - RETURN VALUE - TRUE Yes - FALSE No -*/ - -bool -Events::is_execution_of_events_started() -{ - DBUG_ENTER("Events::is_execution_of_events_started"); - if (unlikely(check_system_tables_error)) - { - my_error(ER_EVENTS_DB_ERROR, MYF(0)); - DBUG_RETURN(FALSE); - } - DBUG_RETURN(scheduler->is_running()); -} + pthread_mutex_lock(&LOCK_event_metadata); - -/* - 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::check_system_tables() - thd Thread - - RETURN VALUE - FALSE OK - TRUE Error -*/ - -bool -Events::check_system_tables(THD *thd) -{ - TABLE_LIST tables; - Open_tables_state backup; - bool ret= FALSE; - - DBUG_ENTER("Events::check_system_tables"); - DBUG_PRINT("enter", ("thd: 0x%lx", (long) 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))) + if (opt_event_scheduler == EVENTS_DISABLED) { - sql_print_error("Event Scheduler: Cannot open mysql.db"); + my_error(ER_OPTION_PREVENTS_STATEMENT, + MYF(0), "--event-scheduler=DISABLED or --skip-grant-tables"); ret= TRUE; + goto end; } - 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; - - if (simple_open_n_lock_tables(thd, &tables)) - { - sql_print_error("Event Scheduler: Cannot open mysql.user"); - ret= TRUE; - } + if (start_or_stop == EVENTS_ON) + ret= scheduler->start(); else + ret= scheduler->stop(); + + if (ret) { - 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); + my_error(ER_EVENT_SET_VAR_ERROR, MYF(0)); + goto end; } - thd->restore_backup_open_tables_state(&backup); + opt_event_scheduler= start_or_stop; +end: + pthread_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); } -/* - 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. +/** + Loads all ENABLED events from mysql.event into a prioritized + queue. - SYNOPSIS - Events::load_events_from_db() - thd Thread context. Used for memory allocation in some cases. + This function is called during the server start up. It reads + every event, computes the next execution time, and if the event + needs execution, adds it to a prioritized queue. Otherwise, if + ON COMPLETION DROP is specified, the event is automatically + removed from the table. - RETURN VALUE - 0 OK - !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, - EVEX_COMPILE_ERROR) - in all these cases mysql.event was - tampered. + @param[in,out] thd Thread context. Used for memory allocation in some cases. - NOTES - Reports the error to the console + @retval FALSE success + @retval TRUE error, the load is aborted + + @note Reports the error to the console */ -int +bool Events::load_events_from_db(THD *thd) { TABLE *table; READ_RECORD read_record_info; - int ret= -1; + bool ret= TRUE; uint count= 0; - bool clean_the_queue= TRUE; DBUG_ENTER("Events::load_events_from_db"); DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd)); - if ((ret= db_repository->open_event_table(thd, TL_READ, &table))) + if ((ret= db_repository->open_event_table(thd, TL_WRITE, &table))) { - sql_print_error("Event Scheduler: Table mysql.event is damaged. Can not open"); - DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); + sql_print_error("Event Scheduler: Failed to open table mysql.event"); + DBUG_RETURN(TRUE); } - init_read_record(&read_record_info, thd, table ,NULL,1,0); + init_read_record(&read_record_info, thd, table, NULL, 0, 1); while (!(read_record_info.read_record(&read_record_info))) { Event_queue_element *et; + bool created; + bool drop_on_completion; + if (!(et= new Event_queue_element)) - { - DBUG_PRINT("info", ("Out of memory")); - break; - } + goto end; + DBUG_PRINT("info", ("Loading event from row.")); - if ((ret= et->load_from_row(thd, table))) + if (et->load_from_row(thd, table)) { sql_print_error("Event Scheduler: " "Error while reading from mysql.event. " "The table is probably corrupted"); - break; - } - if (et->status != Event_queue_element::ENABLED) - { - DBUG_PRINT("info",("%s is disabled",et->name.str)); delete et; - continue; + goto end; } + drop_on_completion= (et->on_completion == + Event_queue_element::ON_COMPLETION_DROP); + - /* let's find when to be executed */ - if (et->compute_next_execution_time()) + if (event_queue->create_event(thd, et, &created)) { - sql_print_error("Event Scheduler: Error while computing execution time of %s.%s." - " Skipping", et->dbname.str, et->name.str); - continue; + /* Out of memory */ + delete et; + goto end; } - + if (created) + count++; + else if (drop_on_completion) { - Event_job_data temp_job_data; - DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); - - temp_job_data.load_from_row(thd, table); - /* - We load only on scheduler root just to check whether the body - compiles. + If not created, a stale event - drop if immediately if + ON COMPLETION NOT PRESERVE */ - switch (ret= temp_job_data.compile(thd, thd->mem_root)) { - case EVEX_MICROSECOND_UNSUP: - sql_print_error("Event Scheduler: mysql.event is tampered. MICROSECOND is not " - "supported but found in mysql.event"); - break; - case EVEX_COMPILE_ERROR: - sql_print_error("Event Scheduler: Error while compiling %s.%s. Aborting load", - et->dbname.str, et->name.str); - break; - default: - break; + int rc= table->file->ha_delete_row(table->record[0]); + if (rc) + { + table->file->print_error(rc, MYF(0)); + goto end; } - 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.", - (long) et)); - event_queue->create_event(thd, et); - count++; } - clean_the_queue= FALSE; + sql_print_information("Event Scheduler: Loaded %d event%s", + count, (count == 1) ? "" : "s"); + ret= FALSE; + end: end_read_record(&read_record_info); - if (clean_the_queue) - { - event_queue->empty_queue(); - ret= -1; - } - else - { - ret= 0; - sql_print_information("Event 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); } |