diff options
author | unknown <andrey@lmy004.> | 2005-12-14 12:15:48 +0100 |
---|---|---|
committer | unknown <andrey@lmy004.> | 2005-12-14 12:15:48 +0100 |
commit | b67555d5d4ff74a4abefdc08f25177d7aa28b5aa (patch) | |
tree | 663635d76e1d9032ac61b9dba1c539cda396d327 /sql | |
parent | 44523ebc50d913c4b0a33e1e9214e60357081ae3 (diff) | |
parent | d22bb45cce40cb015982c193664df08afecb14c7 (diff) | |
download | mariadb-git-b67555d5d4ff74a4abefdc08f25177d7aa28b5aa.tar.gz |
Merge ahristov@bk-internal.mysql.com:/home/bk/mysql-5.1-wl1034
into lmy004.:/work/mysql-5.1-tt-copy-works
libmysqld/Makefile.am:
Auto merged
sql/Makefile.am:
Auto merged
sql/mysqld.cc:
Auto merged
sql/set_var.cc:
Auto merged
sql/sp_head.cc:
Auto merged
sql/sql_acl.cc:
Auto merged
sql/sql_parse.cc:
Auto merged
sql/sql_show.cc:
Auto merged
sql/table.cc:
Auto merged
Diffstat (limited to 'sql')
-rw-r--r-- | sql/Makefile.am | 3 | ||||
-rw-r--r-- | sql/event.cc | 909 | ||||
-rw-r--r-- | sql/event.h | 218 | ||||
-rw-r--r-- | sql/event_executor.cc | 588 | ||||
-rw-r--r-- | sql/event_priv.h | 98 | ||||
-rw-r--r-- | sql/event_timed.cc | 970 | ||||
-rw-r--r-- | sql/lex.h | 8 | ||||
-rw-r--r-- | sql/mysqld.cc | 13 | ||||
-rw-r--r-- | sql/set_var.cc | 5 | ||||
-rw-r--r-- | sql/set_var.h | 11 | ||||
-rw-r--r-- | sql/share/errmsg.txt | 30 | ||||
-rw-r--r-- | sql/sp_head.h | 1 | ||||
-rw-r--r-- | sql/sql_acl.cc | 12 | ||||
-rw-r--r-- | sql/sql_acl.h | 5 | ||||
-rw-r--r-- | sql/sql_lex.cc | 4 | ||||
-rw-r--r-- | sql/sql_lex.h | 7 | ||||
-rw-r--r-- | sql/sql_parse.cc | 68 | ||||
-rw-r--r-- | sql/sql_show.cc | 1 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 456 | ||||
-rw-r--r-- | sql/table.cc | 3 | ||||
-rw-r--r-- | sql/tztime.cc | 12 | ||||
-rw-r--r-- | sql/tztime.h | 1 |
22 files changed, 3386 insertions, 37 deletions
diff --git a/sql/Makefile.am b/sql/Makefile.am index 1090c6d48c7..af08085563d 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -61,7 +61,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ tztime.h my_decimal.h\ sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \ parse_file.h sql_view.h sql_trigger.h \ - sql_array.h sql_cursor.h \ + sql_array.h sql_cursor.h event.h event_priv.h \ sql_plugin.h authors.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -95,6 +95,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ tztime.cc my_time.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_executor.cc event.cc event_timed.cc \ sql_plugin.cc\ handlerton.cc EXTRA_mysqld_SOURCES = ha_innodb.cc ha_berkeley.cc ha_archive.cc \ diff --git a/sql/event.cc b/sql/event.cc new file mode 100644 index 00000000000..806780e5097 --- /dev/null +++ b/sql/event.cc @@ -0,0 +1,909 @@ +/* Copyright (C) 2004-2005 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 "event_priv.h" +#include "event.h" +#include "sp.h" + +/* + TODO list : + - The default value of created/modified should not be 0000-00-00 because of + STRICT mode restricions. + + - CREATE EVENT should not go into binary log! Does it now? The SQL statements + issued by the EVENT are replicated. + I have an idea how to solve the problem at failover. So the status field + will be ENUM('DISABLED', 'ENABLED', 'SLAVESIDE_DISABLED'). + In this case when CREATE EVENT is replicated it should go into the binary + as SLAVESIDE_DISABLED if it is ENABLED, when it's created as DISABLEd it + should be replicated as disabled. If an event is ALTERed as DISABLED the + query should go untouched into the binary log, when ALTERed as enable then + it should go as SLAVESIDE_DISABLED. This is regarding the SQL interface. + TT routines however modify mysql.event internally and this does not go the log + so in this case queries has to be injected into the log...somehow... or + maybe a solution is RBR for this case, because the event may go only from + ENABLED to DISABLED status change and this is safe for replicating. As well + an event may be deleted which is also safe for RBR. + + - Maybe move all allocations during parsing to evex_mem_root thus saving + double parsing in evex_create_event! + + - If the server is killed (stopping) try to kill executing events? + + - What happens if one renames an event in the DB while it is in memory? + Or even deleting it? + + - Consider using conditional variable when doing shutdown instead of + waiting till all worker threads end. + + - Make event_timed::get_show_create_event() work + + - Add function documentation whenever needed. + + - Add logging to file + + - Move comparison code to class event_timed + +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. + +*/ + + +QUEUE EVEX_EQ_NAME; +MEM_ROOT evex_mem_root; + + +void +evex_queue_init(EVEX_QUEUE_TYPE *queue) +{ + if (init_queue_ex(queue, 100 /*num_el*/, 0 /*offset*/, + 0 /*smallest_on_top*/, event_timed_compare_q, NULL, + 100 /*auto_extent*/)) + sql_print_error("Insufficient memory to initialize executing queue."); +} + + +static +int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) +{ + return cs->coll->strnncollsp(cs, + (unsigned char *) s.str,s.length, + (unsigned char *) t.str,t.length, 0); +} + + +int +my_time_compare(TIME *a, TIME *b) +{ +/* + Or maybe it is faster to use TIME_to_ulonglong_datetime + for "a" and "b" +*/ + + DBUG_ENTER("my_time_compare"); + + if (a->year > b->year) + DBUG_RETURN(1); + + if (a->year < b->year) + DBUG_RETURN(-1); + + if (a->month > b->month) + DBUG_RETURN(1); + + if (a->month < b->month) + DBUG_RETURN(-1); + + if (a->day > b->day) + DBUG_RETURN(1); + + if (a->day < b->day) + DBUG_RETURN(-1); + + if (a->hour > b->hour) + DBUG_RETURN(1); + + if (a->hour < b->hour) + DBUG_RETURN(-1); + + if (a->minute > b->minute) + DBUG_RETURN(1); + + if (a->minute < b->minute) + DBUG_RETURN(-1); + + if (a->second > b->second) + DBUG_RETURN(1); + + if (a->second < b->second) + DBUG_RETURN(-1); + + + if (a->second_part > b->second_part) + DBUG_RETURN(1); + + if (a->second_part < b->second_part) + DBUG_RETURN(-1); + + + DBUG_RETURN(0); +} + + +int +evex_time_diff(TIME *a, TIME *b) +{ + my_bool in_gap; + DBUG_ENTER("my_time_diff"); + + return sec_since_epoch_TIME(a) - sec_since_epoch_TIME(b); +} + + +inline int +event_timed_compare(event_timed **a, event_timed **b) +{ + my_ulonglong a_t, b_t; + a_t= TIME_to_ulonglong_datetime(&(*a)->execute_at)*100L + + (*a)->execute_at.second_part; + b_t= TIME_to_ulonglong_datetime(&(*b)->execute_at)*100L + + (*b)->execute_at.second_part; + + if (a_t > b_t) + return 1; + else if (a_t < b_t) + return -1; + else + return 0; + +} + + +int +event_timed_compare_q(void *vptr, byte* a, byte *b) +{ + return event_timed_compare((event_timed **)&a, (event_timed **)&b); +} + + +/* + Open mysql.event table for read + + SYNOPSIS + evex_open_event_table_for_read() + thd Thread context + lock_type How to lock the table + RETURN + 0 Error + # Pointer to TABLE object +*/ + +TABLE *evex_open_event_table(THD *thd, enum thr_lock_type lock_type) +{ + TABLE_LIST tables; + bool not_used; + DBUG_ENTER("open_proc_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(0); + + DBUG_RETURN(tables.table); +} + + +/* + Find row in open mysql.event table representing event + + SYNOPSIS + evex_db_find_event_aux() + thd Thread context + dbname Name of event's database + rname Name of the event inside the db + table TABLE object for open mysql.event table. + + RETURN VALUE + 0 - Routine found + EVEX_KEY_NOT_FOUND - No routine with given name +*/ + +int +evex_db_find_event_aux(THD *thd, const LEX_STRING dbname, + const LEX_STRING ev_name, TABLE *table) +{ + byte key[MAX_KEY_LENGTH]; + DBUG_ENTER("evex_db_find_event_aux"); + DBUG_PRINT("enter", ("name: %.*s", ev_name.length, ev_name.str)); + + /* + Create key to find row. We have to use field->store() to be able to + handle VARCHAR and CHAR fields. + Assumption here is that the two first fields in the table are + 'db' and 'name' and the first key is the primary key over the + same fields. + */ + if (dbname.length > table->field[EVEX_FIELD_DB]->field_length || + ev_name.length > table->field[EVEX_FIELD_NAME]->field_length) + DBUG_RETURN(EVEX_KEY_NOT_FOUND); + + table->field[0]->store(dbname.str, dbname.length, &my_charset_bin); + table->field[1]->store(ev_name.str, ev_name.length, &my_charset_bin); + key_copy(key, table->record[0], table->key_info, table->key_info->key_length); + + if (table->file->index_read_idx(table->record[0], 0, key, + table->key_info->key_length,HA_READ_KEY_EXACT)) + DBUG_RETURN(EVEX_KEY_NOT_FOUND); + + DBUG_RETURN(0); +} + + + +/* + Puts some data common to CREATE and ALTER EVENT into a row. + + SYNOPSIS + evex_fill_row() + thd THD + table the row to fill out + et Event's data + + DESCRIPTION + Used both when an event is created and when it is altered. +*/ + +static int +evex_fill_row(THD *thd, TABLE *table, event_timed *et, my_bool is_update) +{ + DBUG_ENTER("evex_fill_row"); + + if (table->s->fields != EVEX_FIELD_COUNT) + { + my_error(ER_EVENT_COL_COUNT_DOESNT_MATCH, MYF(0), "mysql", "event"); + DBUG_RETURN(EVEX_GET_FIELD_FAILED); + } + + DBUG_PRINT("info", ("dbname.len=%d",et->dbname.length)); + DBUG_PRINT("info", ("name.len=%d",et->name.length)); + + table->field[EVEX_FIELD_DB]-> + store(et->dbname.str, et->dbname.length, system_charset_info); + table->field[EVEX_FIELD_NAME]-> + store(et->name.str, et->name.length, system_charset_info); + + table->field[EVEX_FIELD_ON_COMPLETION]->set_notnull(); + table->field[EVEX_FIELD_ON_COMPLETION]->store((longlong)et->on_completion); + + table->field[EVEX_FIELD_STATUS]->set_notnull(); + table->field[EVEX_FIELD_STATUS]->store((longlong)et->status); +// et->status_changed= false; + + // ToDo: Andrey. How to use users current charset? + if (et->body.str) + table->field[EVEX_FIELD_BODY]-> + store(et->body.str, et->body.length, system_charset_info); + + if (et->starts.year) + { + table->field[EVEX_FIELD_STARTS]->set_notnull();// set NULL flag to OFF + table->field[EVEX_FIELD_STARTS]->store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); + } + + if (et->ends.year) + { + table->field[EVEX_FIELD_ENDS]->set_notnull(); + table->field[EVEX_FIELD_ENDS]->store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); + } + + if (et->expression) + { + table->field[EVEX_FIELD_INTERVAL_EXPR]->set_notnull(); + table->field[EVEX_FIELD_INTERVAL_EXPR]->store((longlong)et->expression); + + table->field[EVEX_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[EVEX_FIELD_TRANSIENT_INTERVAL]->store((longlong)et->interval + 1); + } + else if (et->execute_at.year) + { + // fix_fields already called in init_execute_at + table->field[EVEX_FIELD_EXECUTE_AT]->set_notnull(); + table->field[EVEX_FIELD_EXECUTE_AT]->store_time(&et->execute_at, + MYSQL_TIMESTAMP_DATETIME); + + //this will make it NULL because we don't call set_notnull + table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->store((longlong) 0); + } + 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[EVEX_FIELD_MODIFIED])->set_time(); + + if (et->comment.length) + table->field[EVEX_FIELD_COMMENT]-> + store(et->comment.str, et->comment.length, system_charset_info); + + DBUG_RETURN(0); +} + + +/* + Creates an event in mysql.event + + SYNOPSIS + db_create_event() + thd THD + et event_timed object containing information for the event + + 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". +*/ + +static int +db_create_event(THD *thd, event_timed *et) +{ + int ret= EVEX_OK; + TABLE *table; + char definer[HOSTNAME_LENGTH+USERNAME_LENGTH+2]; + char olddb[128]; + bool dbchanged= false; + DBUG_ENTER("db_create_event"); + DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); + + + DBUG_PRINT("info", ("open mysql.event for update")); + if (!(table= evex_open_event_table(thd, TL_WRITE))) + { + 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->dbname, et->name, table)) + { + my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), et->name.str); + goto err; + } + + DBUG_PRINT("info", ("non-existant, go forward")); + if ((ret= sp_use_new_db(thd, et->dbname.str,olddb, sizeof(olddb),0, &dbchanged))) + { + my_error(ER_BAD_DB_ERROR, MYF(0)); + goto err; + } + + restore_record(table, s->default_values); // Get default values for fields + + if (et->name.length > table->field[EVEX_FIELD_NAME]->field_length) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), et->name.str); + goto err; + } + if (et->body.length > table->field[EVEX_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; + } + + strxmov(definer, et->definer_user.str, "@", et->definer_host.str, NullS); + if ((ret=table->field[EVEX_FIELD_DEFINER]-> + store(definer, et->definer_user.length + 1 + et->definer_host.length, + system_charset_info))) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); + goto err; + } + + ((Field_timestamp *)table->field[EVEX_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->write_row(table->record[0])) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); + goto err; + } + + if (mysql_bin_log.is_open()) + { + thd->clear_error(); + /* Such a statement can always go directly to binlog, no trans cache */ + Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE); + mysql_bin_log.write(&qinfo); + } + + if (dbchanged) + (void) mysql_change_db(thd, olddb, 1); + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_OK); + +err: + if (dbchanged) + (void) mysql_change_db(thd, olddb, 1); + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Used to execute ALTER EVENT. Pendant to evex_update_event(). + + SYNOPSIS + db_update_event() + thd THD + sp_name the name of the event to alter + et event's data + + NOTES + sp_name is passed since this is the name of the event to + alter in case of RENAME TO. +*/ + +static int +db_update_event(THD *thd, event_timed *et, sp_name *new_name) +{ + TABLE *table; + int ret= EVEX_OPEN_TABLE_FAILED; + DBUG_ENTER("db_update_event"); + DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); + if (new_name) + DBUG_PRINT("enter", ("rename to: %.*s", new_name->m_name.length, + new_name->m_name.str)); + + if (!(table= evex_open_event_table(thd, TL_WRITE))) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto err; + } + + // first look whether we overwrite + if (new_name) + { + if (!sortcmp_lex_string(et->name, new_name->m_name, system_charset_info) && + !sortcmp_lex_string(et->dbname, new_name->m_db, system_charset_info)) + { + my_error(ER_EVENT_SAME_NAME, MYF(0), et->name.str); + goto err; + } + + if (!evex_db_find_event_aux(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 whether 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->dbname, et->name, + table)) + { + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); + goto err; + } + + + store_record(table,record[1]); + + // Don't update create on row update. + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; + + // evex_fill_row() calls my_error() in case of error so no need to handle it here + if ((ret= evex_fill_row(thd, table, et, true))) + goto err; + + if (new_name) + { + table->field[EVEX_FIELD_DB]-> + store(new_name->m_db.str, new_name->m_db.length, system_charset_info); + table->field[EVEX_FIELD_NAME]-> + store(new_name->m_name.str, new_name->m_name.length, system_charset_info); + } + + if ((ret= table->file->update_row(table->record[1], table->record[0]))) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); + goto err; + } + + // close mysql.event or we crash later when loading the event from disk + close_thread_tables(thd); + DBUG_RETURN(0); + +err: + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Looks for a named event in mysql.event and in case of success returns + an object will data loaded from the table. + + SYNOPSIS + db_find_event() + thd THD + name the name of the event to find + ett event's data if event is found + tbl TABLE object to use when not NULL + + NOTES + 1) Use sp_name for look up, return in **ett if found + 2) tbl is not closed at exit +*/ + +static int +db_find_event(THD *thd, sp_name *name, event_timed **ett, TABLE *tbl) +{ + TABLE *table; + int ret; + const char *definer; + char *ptr; + event_timed *et; + DBUG_ENTER("db_find_event"); + DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str)); + + if (tbl) + table= tbl; + else if (!(table= evex_open_event_table(thd, TL_READ))) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + ret= EVEX_GENERAL_ERROR; + goto done; + } + + if ((ret= evex_db_find_event_aux(thd, name->m_db, name->m_name, table))) + { + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name->m_name.str); + goto done; + } + et= new event_timed; + + /* + 1)The table should not be closed beforehand. ::load_from_row() only loads + and does not compile + + 2)::load_from_row() is silent on error therefore we emit error msg here + */ + if ((ret= et->load_from_row(&evex_mem_root, table))) + { + my_error(ER_EVENT_CANNOT_LOAD_FROM_TABLE, MYF(0)); + goto done; + } + +done: + if (ret && et) + { + 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); +} + + +/* + Looks for a named event in mysql.event and then loads it from + the table, compiles it and insert it into the cache. + + SYNOPSIS + evex_load_and_compile_event() + thd THD + spn the name of the event to alter + use_lock whether to obtain a lock on LOCK_event_arrays or not + + RETURN VALUE + 0 - OK + < 0 - error (in this case underlying functions call my_error()). + +*/ + +static int +evex_load_and_compile_event(THD * thd, sp_name *spn, bool use_lock) +{ + int ret= 0; + MEM_ROOT *tmp_mem_root; + event_timed *ett; + + DBUG_ENTER("db_load_and_compile_event"); + DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str)); + + tmp_mem_root= thd->mem_root; + thd->mem_root= &evex_mem_root; + + // no need to use my_error() here because db_find_event() has done it + if ((ret= db_find_event(thd, spn, &ett, NULL))) + goto done; + + /* + allocate on evex_mem_root. if you call without evex_mem_root + then sphead will not be cleared! + */ + if ((ret= ett->compile(thd, &evex_mem_root))) + goto done; + + ett->compute_next_execution_time(); + if (use_lock) + VOID(pthread_mutex_lock(&LOCK_event_arrays)); + + evex_queue_insert(&EVEX_EQ_NAME, (EVEX_PTOQEL) ett); + + /* + There is a copy in the array which we don't need. sphead won't be + destroyed. + */ + + if (use_lock) + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + +done: + if (thd->mem_root != tmp_mem_root) + thd->mem_root= tmp_mem_root; + + DBUG_RETURN(ret); +} + + +static int +evex_remove_from_cache(LEX_STRING *db, LEX_STRING *name, bool use_lock) +{ + uint i; + + DBUG_ENTER("evex_remove_from_cache"); + /* + It is possible that 2 (or 1) pass(es) won't find the event in memory. + The reason is that DISABLED events are not cached. + */ + + if (use_lock) + VOID(pthread_mutex_lock(&LOCK_event_arrays)); + + for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i) + { + event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, event_timed*); + DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?",db->str,name->str, et->dbname.str, + et->name.str)); + if (!sortcmp_lex_string(*name, et->name, system_charset_info) && + !sortcmp_lex_string(*db, et->dbname, system_charset_info)) + { + et->free_sp(); + delete et; + evex_queue_delete_element(&EVEX_EQ_NAME, i); + // ok, we have cleaned + goto done; + } + } + +done: + if (use_lock) + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + + DBUG_RETURN(0); +} + + + + +/* + The function exported to the world for creating of events. + + SYNOPSIS + evex_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 + + 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. +*/ + +int +evex_create_event(THD *thd, event_timed *et, uint create_options) +{ + int ret = 0; + + DBUG_ENTER("evex_create_event"); + DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length, + et->name.str, create_options)); + + if ((ret = db_create_event(thd, et)) == EVEX_WRITE_ROW_FAILED && + (create_options & HA_LEX_CREATE_IF_NOT_EXISTS)) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_DB_CREATE_EXISTS, ER(ER_DB_CREATE_EXISTS), + "EVENT", et->name.str); + ret= 0; + goto done; + } + /* + A warning is thrown only when create_options is set to + HA_LEX_CREATE_IF_NOT_EXISTS. In this case if EVEX_WRITE_ROW_FAILED, + which means that we have duplicated key -> warning. In all + other cases -> error. + */ + if (ret) + goto done; + + VOID(pthread_mutex_lock(&LOCK_evex_running)); + if (evex_is_running && et->status == MYSQL_EVENT_ENABLED) + { + sp_name spn(et->dbname, et->name); + ret= evex_load_and_compile_event(thd, &spn, true); + } + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + +done: + // No need to close the table, it will be closed in sql_parse::do_command + + DBUG_RETURN(ret); +} + + +/* + The function exported to the world for alteration of events. + + SYNOPSIS + evex_update_event() + thd THD + et event's data + new_name set in case of RENAME TO. + + NOTES + et contains data about dbname and event name. + name is the new name of the event, if not null this means + that RENAME TO was specified in the query. +*/ + +int +evex_update_event(THD *thd, event_timed *et, sp_name *new_name) +{ + int ret, i; + bool need_second_pass= true; + + DBUG_ENTER("evex_update_event"); + DBUG_PRINT("enter", ("name: %*s", et->name.length, et->name.str)); + + /* + db_update_event() opens & closes the table to prevent + crash later in the code when loading and compiling the new definition. + Also on error conditions my_error() is called so no need to handle here + */ + if ((ret= db_update_event(thd, et, new_name))) + goto done; + + VOID(pthread_mutex_lock(&LOCK_evex_running)); + if (!evex_is_running) + UNLOCK_MUTEX_AND_BAIL_OUT(LOCK_evex_running, done); + + VOID(pthread_mutex_lock(&LOCK_event_arrays)); + evex_remove_from_cache(&et->dbname, &et->name, false); + if (et->status == MYSQL_EVENT_ENABLED) + { + if (new_name) + ret= evex_load_and_compile_event(thd, new_name, false); + else + { + sp_name spn(et->dbname, et->name); + ret= evex_load_and_compile_event(thd, &spn, false); + } + if (ret == EVEX_COMPILE_ERROR) + my_error(ER_EVENT_COMPILE_ERROR, MYF(0)); + } + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + +done: + DBUG_RETURN(ret); +} + + +/* + Drops an event + + SYNOPSIS + evex_drop_event() + thd THD + et event's name + drop_if_exists if set and the event not existing => warning onto the stack + +*/ + +int +evex_drop_event(THD *thd, event_timed *et, bool drop_if_exists) +{ + TABLE *table; + int ret= EVEX_OPEN_TABLE_FAILED; + bool opened; + DBUG_ENTER("evex_drop_event"); + + if (!(table= evex_open_event_table(thd, TL_WRITE))) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto done; + } + + if (!(ret= evex_db_find_event_aux(thd, et->dbname, et->name, table))) + { + if ((ret= table->file->delete_row(table->record[0]))) + { + my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); + goto done; + } + } + 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; + } + + VOID(pthread_mutex_lock(&LOCK_evex_running)); + if (evex_is_running) + ret= evex_remove_from_cache(&et->dbname, &et->name, true); + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + +done: + /* + No need to close the table, it will be closed in sql_parse::do_command() + and evex_remove_from_cache does not try to open a table + */ + + DBUG_RETURN(ret); +} + diff --git a/sql/event.h b/sql/event.h new file mode 100644 index 00000000000..f3b49a99488 --- /dev/null +++ b/sql/event.h @@ -0,0 +1,218 @@ +/* Copyright (C) 2004-2005 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 */ + +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#include "sp.h" +#include "sp_head.h" + +#define EVEX_OK SP_OK +#define EVEX_KEY_NOT_FOUND SP_KEY_NOT_FOUND +#define EVEX_OPEN_TABLE_FAILED SP_OPEN_TABLE_FAILED +#define EVEX_WRITE_ROW_FAILED SP_WRITE_ROW_FAILED +#define EVEX_DELETE_ROW_FAILED SP_DELETE_ROW_FAILED +#define EVEX_GET_FIELD_FAILED SP_GET_FIELD_FAILED +#define EVEX_PARSE_ERROR SP_PARSE_ERROR +#define EVEX_INTERNAL_ERROR SP_INTERNAL_ERROR +#define EVEX_NO_DB_ERROR SP_NO_DB_ERROR +#define EVEX_COMPILE_ERROR -19 +#define EVEX_GENERAL_ERROR -20 +#define EVEX_BAD_IDENTIFIER SP_BAD_IDENTIFIER +#define EVEX_BODY_TOO_LONG SP_BODY_TOO_LONG +#define EVEX_BAD_PARAMS -21 +#define EVEX_NOT_RUNNING -22 + +#define EVENT_EXEC_NO_MORE (1L << 0) +#define EVENT_NOT_USED (1L << 1) + + +extern ulong opt_event_executor; + +enum enum_event_on_completion +{ + MYSQL_EVENT_ON_COMPLETION_DROP = 1, + MYSQL_EVENT_ON_COMPLETION_PRESERVE +}; + +enum enum_event_status +{ + MYSQL_EVENT_ENABLED = 1, + MYSQL_EVENT_DISABLED +}; + + +class event_timed +{ + event_timed(const event_timed &); /* Prevent use of these */ + void operator=(event_timed &); + my_bool running; + pthread_mutex_t LOCK_running; + + bool status_changed; + bool last_executed_changed; + TIME last_executed; + +public: + LEX_STRING dbname; + LEX_STRING name; + LEX_STRING body; + + LEX_STRING definer_user; + LEX_STRING definer_host; + LEX_STRING definer;// combination of user and host + + LEX_STRING comment; + TIME starts; + TIME ends; + TIME execute_at; + + longlong expression; + interval_type interval; + + longlong created; + longlong modified; + enum enum_event_on_completion on_completion; + enum enum_event_status status; + sp_head *sphead; + + const uchar *body_begin; + + bool dropped; + bool free_sphead_on_delete; + uint flags;//all kind of purposes + + event_timed():running(0), status_changed(false), last_executed_changed(false), + expression(0), created(0), modified(0), + on_completion(MYSQL_EVENT_ON_COMPLETION_DROP), + status(MYSQL_EVENT_ENABLED), sphead(0), dropped(false), + free_sphead_on_delete(true), flags(0) + + { + pthread_mutex_init(&LOCK_running, MY_MUTEX_INIT_FAST); + init(); + } + + ~event_timed() + { + pthread_mutex_destroy(&LOCK_running); + if (free_sphead_on_delete) + free_sp(); + } + + void + init(); + + 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 + event_timed::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(); + + void + mark_last_executed(); + + bool + drop(THD *thd); + + bool + update_fields(THD *thd); + + char * + get_show_create_event(THD *thd, uint *length); + + int + execute(THD *thd, MEM_ROOT *mem_root= NULL); + + int + compile(THD *thd, MEM_ROOT *mem_root= NULL); + + void free_sp() + { + delete sphead; + sphead= 0; + } +}; + + +int +evex_create_event(THD *thd, event_timed *et, uint create_options); + +int +evex_update_event(THD *thd, event_timed *et, sp_name *new_name); + +int +evex_drop_event(THD *thd, event_timed *et, bool drop_if_exists); + + +int +init_events(); + +void +shutdown_events(); + + +// auxiliary +int +event_timed_compare(event_timed **a, event_timed **b); + + +/* +CREATE TABLE `event` ( + `db` varchar(64) character set utf8 collate utf8_bin NOT NULL default '', + `name` varchar(64) character set utf8 collate utf8_bin NOT NULL default '', + `body` longblob NOT NULL, + `definer` varchar(77) character set utf8 collate utf8_bin NOT NULL default '', + `execute_at` datetime default NULL, + `transient_expression` int(11) default NULL, + `interval_type` 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') default NULL, + `created` timestamp NOT NULL, + `modified` timestamp NOT NULL, + `last_executed` datetime default NULL, + `starts` datetime default NULL, + `ends` datetime default NULL, + `status` enum('ENABLED','DISABLED') NOT NULL default 'ENABLED', + `on_completion` enum('DROP','PRESERVE') NOT NULL default 'DROP', + `comment` varchar(64) character set utf8 collate utf8_bin NOT NULL default '', + PRIMARY KEY (`db`,`name`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 +*/ + +#endif /* _EVENT_H_ */ diff --git a/sql/event_executor.cc b/sql/event_executor.cc new file mode 100644 index 00000000000..623e45bf118 --- /dev/null +++ b/sql/event_executor.cc @@ -0,0 +1,588 @@ +/* Copyright (C) 2004-2005 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 "event_priv.h" +#include "event.h" +#include "sp.h" + + +/* + Make this define DBUG_FAULTY_THR to be able to put breakpoints inside + code used by the scheduler's thread(s). In this case user connections + are not possible because the scheduler thread code is ran inside the + main thread (no spawning takes place. If you want to debug client + connection then start with --one-thread and make the define + DBUG_FAULTY_THR ! +*/ +#define DBUG_FAULTY_THR2 + +extern ulong thread_created; + + +pthread_mutex_t LOCK_event_arrays, + LOCK_workers_count, + LOCK_evex_running; + + +bool evex_is_running= false; + +ulonglong evex_main_thread_id= 0; +ulong opt_event_executor; +volatile my_bool event_executor_running_global_var; +static my_bool evex_mutexes_initted= false; +static uint workers_count; + +static int +evex_load_events_from_db(THD *thd); + + + +/* + TODO Andrey: Check for command line option whether to start + the main thread or not. +*/ + +pthread_handler_t +event_executor_worker(void *arg); + +pthread_handler_t +event_executor_main(void *arg); + +static +void evex_init_mutexes() +{ + if (evex_mutexes_initted) + return; + + evex_mutexes_initted= true; + pthread_mutex_init(&LOCK_event_arrays, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_workers_count, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_evex_running, MY_MUTEX_INIT_FAST); + + event_executor_running_global_var= opt_event_executor; +} + + +int +init_events() +{ + pthread_t th; + + DBUG_ENTER("init_events"); + + DBUG_PRINT("info",("Starting events main thread")); + + evex_init_mutexes(); + + VOID(pthread_mutex_lock(&LOCK_evex_running)); + evex_is_running= false; + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + +#ifndef DBUG_FAULTY_THR + //TODO Andrey: Change the error code returned! + if (pthread_create(&th, NULL, event_executor_main, (void*)NULL)) + DBUG_RETURN(ER_SLAVE_THREAD); +#else + event_executor_main(NULL); +#endif + + DBUG_RETURN(0); +} + + +void +shutdown_events() +{ + DBUG_ENTER("shutdown_events"); + + VOID(pthread_mutex_lock(&LOCK_evex_running)); + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + + pthread_mutex_destroy(&LOCK_event_arrays); + pthread_mutex_destroy(&LOCK_workers_count); + pthread_mutex_destroy(&LOCK_evex_running); + + DBUG_VOID_RETURN; +} + + +static int +init_event_thread(THD* thd) +{ + DBUG_ENTER("init_event_thread"); + thd->client_capabilities= 0; + thd->security_ctx->skip_grants(); + my_net_init(&thd->net, 0); + thd->net.read_timeout = slave_net_timeout; + thd->slave_thread= 0; + thd->options= OPTION_AUTO_IS_NULL; + thd->client_capabilities= CLIENT_LOCAL_FILES; + thd->real_id=pthread_self(); + VOID(pthread_mutex_lock(&LOCK_thread_count)); + thd->thread_id= thread_id++; + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + + if (init_thr_lock() || thd->store_globals()) + { + thd->cleanup(); + delete thd; + 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 + + thd->proc_info= "Initialized"; + thd->version= refresh_version; + thd->set_time(); + DBUG_RETURN(0); +} + +pthread_handler_t +event_executor_main(void *arg) +{ + THD *thd; /* needs to be first for thread_stack */ + ulonglong iter_num= 0; + uint i=0, j=0; + my_ulonglong cnt= 0; + + DBUG_ENTER("event_executor_main"); + DBUG_PRINT("event_executor_main", ("EVEX thread started")); + + + // init memory root + init_alloc_root(&evex_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + + + // needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff + my_thread_init(); + + //TODO Andrey: Check for NULL + if (!(thd = new THD)) // note that contructor of THD uses DBUG_ ! + { + sql_print_error("Cannot create THD for event_executor_main"); + goto err_no_thd; + } + thd->thread_stack = (char*)&thd; // remember where our stack is + + pthread_detach_this_thread(); + + if (init_event_thread(thd)) + goto err; + + // make this thread invisible it has no vio -> show processlist won't see + thd->system_thread= 1; + + VOID(pthread_mutex_lock(&LOCK_thread_count)); + threads.append(thd); + thread_count++; + thread_running++; + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + + DBUG_PRINT("EVEX main thread", ("Initing events_queuey")); + + /* + eventually manifest that we are running, not to crashe because of + usage of non-initialized memory structures. + */ + VOID(pthread_mutex_lock(&LOCK_evex_running)); + VOID(pthread_mutex_lock(&LOCK_event_arrays)); + evex_queue_init(&EVEX_EQ_NAME); + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + evex_is_running= true; + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + + if (evex_load_events_from_db(thd)) + goto err; + + THD_CHECK_SENTRY(thd); + /* Read queries from the IO/THREAD until this thread is killed */ + evex_main_thread_id= thd->thread_id; + sql_print_information("Scheduler thread started"); + while (!thd->killed) + { + TIME time_now; + my_time_t now; + event_timed *et; + + cnt++; + DBUG_PRINT("info", ("EVEX External Loop %d", cnt)); + + thd->proc_info = "Sleeping"; + if (!evex_queue_num_elements(EVEX_EQ_NAME) || + !event_executor_running_global_var) + { + my_sleep(1000000);// sleep 1s + continue; + } + + { + int t2sleep; + + + /* + now let's see how much time to sleep, we know there is at least 1 + element in the queue. + */ + VOID(pthread_mutex_lock(&LOCK_event_arrays)); + if (!evex_queue_num_elements(EVEX_EQ_NAME)) + { + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + continue; + } + et= evex_queue_first_element(&EVEX_EQ_NAME, event_timed*); + + time(&now); + my_tz_UTC->gmt_sec_to_TIME(&time_now, now); + t2sleep= evex_time_diff(&et->execute_at, &time_now); + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + if (t2sleep > 0) + { + sql_print_information("Sleeping for %d seconds.", t2sleep); + printf("\nWHEN=%llu NOW=%llu\n", TIME_to_ulonglong_datetime(&et->execute_at), TIME_to_ulonglong_datetime(&time_now)); + /* + We sleep t2sleep seconds but we check every second whether this thread + has been killed, or there is new candidate + */ + while (t2sleep-- && !thd->killed && + evex_queue_num_elements(EVEX_EQ_NAME) && + (evex_queue_first_element(&EVEX_EQ_NAME, event_timed*) == et)) + my_sleep(1000000); + sql_print_information("Finished sleeping"); + } + if (!event_executor_running_global_var) + continue; + + } + + + VOID(pthread_mutex_lock(&LOCK_event_arrays)); + + if (!evex_queue_num_elements(EVEX_EQ_NAME)) + { + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + continue; + } + et= evex_queue_first_element(&EVEX_EQ_NAME, event_timed*); + + /* + if this is the first event which is after time_now then no + more need to iterate over more elements since the array is sorted. + */ + if (et->execute_at.year > 1969 && + my_time_compare(&time_now, &et->execute_at) == -1) + { + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + continue; + } + + if (et->status == MYSQL_EVENT_ENABLED) + { + pthread_t th; + + DBUG_PRINT("info", (" Spawning a thread %d", ++iter_num)); + sql_print_information(" Spawning a thread %d", ++iter_num); +#ifndef DBUG_FAULTY_THR + sql_print_information(" Thread is not debuggable!"); + if (pthread_create(&th, NULL, event_executor_worker, (void*)et)) + { + sql_print_error("Problem while trying to create a thread"); + UNLOCK_MUTEX_AND_BAIL_OUT(LOCK_event_arrays, err); + } +#else + event_executor_worker((void *) et); +#endif + printf("[%10s] exec at [%llu]\n", et->name.str,TIME_to_ulonglong_datetime(&et->execute_at)); + et->mark_last_executed(); + et->compute_next_execution_time(); + printf("[%10s] next at [%llu]\n\n\n", et->name.str,TIME_to_ulonglong_datetime(&et->execute_at)); + et->update_fields(thd); + if ((et->execute_at.year && !et->expression) || + TIME_to_ulonglong_datetime(&et->execute_at) == 0L) + et->flags |= EVENT_EXEC_NO_MORE; + } + if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == MYSQL_EVENT_DISABLED) + { + if (et->dropped) + et->drop(thd); + delete et; + evex_queue_delete_element(&EVEX_EQ_NAME, 1);// 1 is top + } else + evex_queue_first_updated(&EVEX_EQ_NAME); + + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + }// while + +err: + // First manifest that this thread does not work and then destroy + VOID(pthread_mutex_lock(&LOCK_evex_running)); + evex_is_running= false; + evex_main_thread_id= 0; + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + + sql_print_information("Event scheduler stopping"); + + /* + TODO: A better will be with a conditional variable + */ + /* + Read workers_count without lock, no need for locking. + In the worst case we have to wait 1sec more. + */ + while (workers_count) + my_sleep(1000000);// 1s + + /* + LEX_STRINGs reside in the memory root and will be destroyed with it. + Hence no need of delete but only freeing of SP + */ + // First we free all objects ... + for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i) + { + event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, event_timed*); + et->free_sp(); + delete et; + } + // ... then we can thras the whole queue at once + evex_queue_destroy(&EVEX_EQ_NAME); + + thd->proc_info = "Clearing"; + DBUG_ASSERT(thd->net.buff != 0); + net_end(&thd->net); // destructor will not free it, because we are weird + THD_CHECK_SENTRY(thd); + + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; +#ifndef DBUG_FAULTY_THR + THD_CHECK_SENTRY(thd); + delete thd; +#endif + pthread_mutex_unlock(&LOCK_thread_count); + + +err_no_thd: + VOID(pthread_mutex_lock(&LOCK_evex_running)); + evex_is_running= false; + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + + free_root(&evex_mem_root, MYF(0)); + sql_print_information("Event scheduler stopped"); + +#ifndef DBUG_FAULTY_THR + my_thread_end(); + pthread_exit(0); +#endif + DBUG_RETURN(0);// Can't return anything here +} + + +pthread_handler_t +event_executor_worker(void *event_void) +{ + THD *thd; /* needs to be first for thread_stack */ + event_timed *event = (event_timed *) event_void; + MEM_ROOT worker_mem_root; + + DBUG_ENTER("event_executor_worker"); + VOID(pthread_mutex_lock(&LOCK_workers_count)); + ++workers_count; + VOID(pthread_mutex_unlock(&LOCK_workers_count)); + + init_alloc_root(&worker_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + +#ifndef DBUG_FAULTY_THR + my_thread_init(); + + if (!(thd = new THD)) // note that contructor of THD uses DBUG_ ! + { + sql_print_error("Cannot create a THD structure in a scheduler worker thread"); + goto err_no_thd; + } + thd->thread_stack = (char*)&thd; // remember where our stack is + thd->mem_root= &worker_mem_root; + + pthread_detach(pthread_self()); + + if (init_event_thread(thd)) + goto err; + + thd->init_for_queries(); + + // make this thread visible it has no vio -> show processlist needs this flag + thd->system_thread= 1; + + VOID(pthread_mutex_lock(&LOCK_thread_count)); + threads.append(thd); + thread_count++; + thread_running++; + VOID(pthread_mutex_unlock(&LOCK_thread_count)); +#else + thd= current_thd; +#endif + + // thd->security_ctx->priv_host is char[MAX_HOSTNAME] + + strxnmov(thd->security_ctx->priv_host, sizeof(thd->security_ctx->priv_host), + event->definer_host.str, NullS); + + thd->security_ctx->priv_user= event->definer_user.str; + + thd->db= event->dbname.str; + if (!check_access(thd, EVENT_ACL, event->dbname.str, 0, 0, 0, + is_schema_db(event->dbname.str))) + { + char exec_time[200]; + int ret; + my_TIME_to_str(&event->execute_at, exec_time); + DBUG_PRINT("info", (" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->dbname.str, event->name.str,(int) event->expression, exec_time)); + sql_print_information(" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->dbname.str, event->name.str,(int) event->expression, exec_time); + ret= event->execute(thd, &worker_mem_root); + sql_print_information(" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]. RetCode=%d", event->dbname.str, event->name.str,(int) event->expression, exec_time, ret); + DBUG_PRINT("info", (" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->dbname.str, event->name.str,(int) event->expression, exec_time)); + } + thd->db= 0; + +err: + VOID(pthread_mutex_lock(&LOCK_thread_count)); +#ifndef DBUG_FAULTY_THR + thread_count--; + thread_running--; + /* + Some extra safety, which should not been needed (normally, event deletion + should already have done these assignments (each event which sets these + variables is supposed to set them to 0 before terminating)). + */ + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + + thd->proc_info = "Clearing"; + DBUG_ASSERT(thd->net.buff != 0); + net_end(&thd->net); // destructor will not free it, because we are weird + THD_CHECK_SENTRY(thd); + + VOID(pthread_mutex_lock(&LOCK_thread_count)); + THD_CHECK_SENTRY(thd); + delete thd; +#endif + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + +err_no_thd: + + free_root(&worker_mem_root, MYF(0)); + + VOID(pthread_mutex_lock(&LOCK_workers_count)); + --workers_count; + VOID(pthread_mutex_unlock(&LOCK_workers_count)); + +#ifndef DBUG_FAULTY_THR + my_thread_end(); + pthread_exit(0); +#endif + DBUG_RETURN(0); // Can't return anything here +} + + +static int +evex_load_events_from_db(THD *thd) +{ + TABLE *table; + READ_RECORD read_record_info; + MYSQL_LOCK *lock; + int ret= -1; + + DBUG_ENTER("evex_load_events_from_db"); + + if (!(table= evex_open_event_table(thd, TL_READ))) + DBUG_RETURN(SP_OPEN_TABLE_FAILED); + + VOID(pthread_mutex_lock(&LOCK_event_arrays)); + + 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("evex_load_events_from_db", ("Out of memory")); + ret= -1; + goto end; + } + DBUG_PRINT("evex_load_events_from_db", ("Loading event from row.")); + + if ((ret= et->load_from_row(&evex_mem_root, table))) + { + sql_print_error("Error while loading from mysql.event. " + "Table probably corrupted"); + goto end; + } + if (et->status != MYSQL_EVENT_ENABLED) + { + DBUG_PRINT("evex_load_events_from_db",("Event %s is disabled", et->name.str)); + delete et; + continue; + } + + DBUG_PRINT("evex_load_events_from_db", + ("Event %s loaded from row. Time to compile", et->name.str)); + + if ((ret= et->compile(thd, &evex_mem_root))) + { + sql_print_error("Error while compiling %s.%s. Aborting load.", + et->dbname.str, et->name.str); + goto end; + } + // let's find when to be executed + et->compute_next_execution_time(); + + DBUG_PRINT("evex_load_events_from_db", ("Adding to the exec list.")); + + evex_queue_insert(&EVEX_EQ_NAME, (EVEX_PTOQEL) et); + DBUG_PRINT("evex_load_events_from_db", ("%p %*s", + et, et->name.length,et->name.str)); + } + + ret= 0; + +end: + VOID(pthread_mutex_unlock(&LOCK_event_arrays)); + end_read_record(&read_record_info); + + thd->version--; // Force close to free memory + + close_thread_tables(thd); + + DBUG_PRINT("info", ("Finishing with status code %d", ret)); + DBUG_RETURN(ret); +} + + +bool sys_var_event_executor::update(THD *thd, set_var *var) +{ + // here start the thread if not running. + VOID(pthread_mutex_lock(&LOCK_evex_running)); + *value= var->save_result.ulong_value; + if ((my_bool) *value && !evex_is_running) + { + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + init_events(); + } else + VOID(pthread_mutex_unlock(&LOCK_evex_running)); + return 0; +} + diff --git a/sql/event_priv.h b/sql/event_priv.h new file mode 100644 index 00000000000..cb45a700cb8 --- /dev/null +++ b/sql/event_priv.h @@ -0,0 +1,98 @@ +/* Copyright (C) 2004-2005 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 */ + +#ifndef _EVENT_PRIV_H_ +#define _EVENT_PRIV_H_ +#include "mysql_priv.h" + + +#define EVEX_USE_QUEUE + +#define UNLOCK_MUTEX_AND_BAIL_OUT(__mutex, __label) \ + { VOID(pthread_mutex_unlock(&__mutex)); goto __label; } + +enum +{ + EVEX_FIELD_DB = 0, + EVEX_FIELD_NAME, + EVEX_FIELD_BODY, + EVEX_FIELD_DEFINER, + EVEX_FIELD_EXECUTE_AT, + EVEX_FIELD_INTERVAL_EXPR, + EVEX_FIELD_TRANSIENT_INTERVAL, + EVEX_FIELD_CREATED, + EVEX_FIELD_MODIFIED, + EVEX_FIELD_LAST_EXECUTED, + EVEX_FIELD_STARTS, + EVEX_FIELD_ENDS, + EVEX_FIELD_STATUS, + EVEX_FIELD_ON_COMPLETION, + EVEX_FIELD_COMMENT, + EVEX_FIELD_COUNT /* a cool trick to count the number of fields :) */ +}; + + + +int +my_time_compare(TIME *a, TIME *b); + +int +evex_db_find_event_aux(THD *thd, const LEX_STRING dbname, + const LEX_STRING rname, TABLE *table); + +TABLE * +evex_open_event_table(THD *thd, enum thr_lock_type lock_type); + +int +event_timed_compare_q(void *vptr, byte* a, byte *b); + +int +evex_time_diff(TIME *a, TIME *b); + + + +#define EXEC_QUEUE_QUEUE_NAME executing_queue +#define EXEC_QUEUE_DARR_NAME evex_executing_queue + + +#define EVEX_QUEUE_TYPE QUEUE +#define EVEX_PTOQEL byte * + +#define EVEX_EQ_NAME executing_queue +#define evex_queue_first_element(queue, __cast) ((__cast)queue_top(queue)) +#define evex_queue_element(queue, idx, __cast) ((__cast)queue_element(queue, idx)) +#define evex_queue_delete_element(queue, idx) queue_remove(queue, idx) +#define evex_queue_destroy(queue) delete_queue(queue) +#define evex_queue_first_updated(queue) queue_replaced(queue) +#define evex_queue_insert(queue, element) queue_insert_safe(queue, element); + + + +void +evex_queue_init(EVEX_QUEUE_TYPE *queue); + +#define evex_queue_num_elements(queue) queue.elements + + +extern bool evex_is_running; +extern MEM_ROOT evex_mem_root; +extern pthread_mutex_t LOCK_event_arrays, + LOCK_workers_count, + LOCK_evex_running; +extern ulonglong evex_main_thread_id; +extern QUEUE EVEX_EQ_NAME; + +#endif /* _EVENT_PRIV_H_ */ diff --git a/sql/event_timed.cc b/sql/event_timed.cc new file mode 100644 index 00000000000..747eab4558c --- /dev/null +++ b/sql/event_timed.cc @@ -0,0 +1,970 @@ +/* Copyright (C) 2004-2005 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 "event_priv.h" +#include "event.h" +#include "sp.h" + + + +extern int yyparse(void *thd); + +/* + Init all member variables + + SYNOPSIS + event_timed::init() +*/ + +void +event_timed::init() +{ + DBUG_ENTER("event_timed::init"); + + dbname.str= name.str= body.str= comment.str= 0; + dbname.length= name.length= body.length= comment.length= 0; + + set_zero_time(&starts, MYSQL_TIMESTAMP_DATETIME); + set_zero_time(&ends, MYSQL_TIMESTAMP_DATETIME); + set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); + set_zero_time(&last_executed, MYSQL_TIMESTAMP_DATETIME); + + definer_user.str= definer_host.str= 0; + definer_user.length= definer_host.length= 0; + + DBUG_VOID_RETURN; +} + + +/* + Set a name of the event + + SYNOPSIS + event_timed::init_name() + thd THD + spn the name extracted in the parser +*/ + +void +event_timed::init_name(THD *thd, sp_name *spn) +{ + DBUG_ENTER("event_timed::init_name"); + uint n; /* Counter for nul trimming */ + /* During parsing, we must use thd->mem_root */ + MEM_ROOT *root= thd->mem_root; + + /* We have to copy strings to get them into the right memroot */ + if (spn) + { + dbname.length= spn->m_db.length; + if (spn->m_db.length == 0) + dbname.str= NULL; + else + dbname.str= strmake_root(root, spn->m_db.str, spn->m_db.length); + name.length= spn->m_name.length; + name.str= strmake_root(root, spn->m_name.str, spn->m_name.length); + + if (spn->m_qname.length == 0) + spn->init_qname(thd); + } + else if (thd->db) + { + dbname.length= thd->db_length; + dbname.str= strmake_root(root, thd->db, dbname.length); + } + + DBUG_PRINT("dbname", ("len=%d db=%s",dbname.length, dbname.str)); + DBUG_PRINT("name", ("len=%d name=%s",name.length, name.str)); + + DBUG_VOID_RETURN; +} + + +/* + Set body of the event - what should be executed. + + SYNOPSIS + event_timed::init_body() + thd THD + + NOTE + The body is extracted by copying all data between the + start of the body set by another method and the current pointer in Lex. +*/ + +void +event_timed::init_body(THD *thd) +{ + DBUG_ENTER("event_timed::init_body"); + MEM_ROOT *root= thd->mem_root; + + body.length= thd->lex->ptr - body_begin; + // Trim nuls at the end + while (body.length && body_begin[body.length-1] == '\0') + body.length--; + + body.str= strmake_root(root, (char *)body_begin, body.length); + + DBUG_VOID_RETURN; +} + + +/* + Set time for execution for one time events. + + SYNOPSIS + event_timed::init_execute_at() + expr when (datetime) + + RETURNS + 0 - OK + EVEX_PARSE_ERROR - fix_fields failed + EVEX_BAD_PARAMS - datetime is in the past +*/ + +int +event_timed::init_execute_at(THD *thd, Item *expr) +{ + my_bool not_used; + TIME ltime; + my_time_t my_time_tmp; + + TIME time_tmp; + DBUG_ENTER("event_timed::init_execute_at"); + + if (expr->fix_fields(thd, &expr)) + DBUG_RETURN(EVEX_PARSE_ERROR); + + if (expr->val_int() == MYSQL_TIMESTAMP_ERROR) + DBUG_RETURN(EVEX_BAD_PARAMS); + + // let's check whether time is in the past + thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp, + (my_time_t) thd->query_start()); + + if (expr->val_int() < TIME_to_ulonglong_datetime(&time_tmp)) + DBUG_RETURN(EVEX_BAD_PARAMS); + + if ((not_used= expr->get_date(<ime, TIME_NO_ZERO_DATE))) + DBUG_RETURN(EVEX_BAD_PARAMS); + + /* + This may result in a 1970-01-01 date if ltime is > 2037-xx-xx + CONVERT_TZ has similar problem + */ + my_tz_UTC->gmt_sec_to_TIME(<ime, TIME_to_timestamp(thd,<ime, ¬_used)); + + + execute_at= ltime; + DBUG_RETURN(0); +} + + +/* + Set time for execution for transient events. + + SYNOPSIS + event_timed::init_interval() + expr how much? + new_interval what is the interval + + RETURNS + 0 - OK + EVEX_PARSE_ERROR - fix_fields failed + EVEX_BAD_PARAMS - Interval is not positive +*/ + +int +event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) +{ + longlong tmp; + DBUG_ENTER("event_timed::init_interval"); + + if (expr->fix_fields(thd, &expr)) + DBUG_RETURN(EVEX_PARSE_ERROR); + + if ((tmp= expr->val_int()) <= 0) + DBUG_RETURN(EVEX_BAD_PARAMS); + + expression= tmp; + interval= new_interval; + DBUG_RETURN(0); +} + + +/* + Set activation time. + + SYNOPSIS + event_timed::init_starts() + expr how much? + interval what is the interval + + NOTES + Note that activation time is not execution time. + EVERY 5 MINUTE STARTS "2004-12-12 10:00:00" means that + the event will be executed every 5 minutes but this will + start at the date shown above. Expressions are possible : + DATE_ADD(NOW(), INTERVAL 1 DAY) -- start tommorow at + same time. + + RETURNS + 0 - OK + EVEX_PARSE_ERROR - fix_fields failed +*/ + +int +event_timed::init_starts(THD *thd, Item *new_starts) +{ + my_bool not_used; + TIME ltime; + my_time_t my_time_tmp; + + DBUG_ENTER("event_timed::init_starts"); + + if (new_starts->fix_fields(thd, &new_starts)) + DBUG_RETURN(EVEX_PARSE_ERROR); + + if (new_starts->val_int() == MYSQL_TIMESTAMP_ERROR) + DBUG_RETURN(EVEX_BAD_PARAMS); + + if ((not_used= new_starts->get_date(<ime, TIME_NO_ZERO_DATE))) + DBUG_RETURN(EVEX_BAD_PARAMS); + + /* + This may result in a 1970-01-01 date if ltime is > 2037-xx-xx + CONVERT_TZ has similar problem + */ + my_tz_UTC->gmt_sec_to_TIME(<ime, TIME_to_timestamp(thd, <ime, ¬_used)); + + starts= ltime; + DBUG_RETURN(0); +} + + +/* + Set deactivation time. + + SYNOPSIS + event_timed::init_ends() + thd THD + new_ends when? + + NOTES + Note that activation time is not execution time. + EVERY 5 MINUTE ENDS "2004-12-12 10:00:00" means that + the event will be executed every 5 minutes but this will + end at the date shown above. Expressions are possible : + DATE_ADD(NOW(), INTERVAL 1 DAY) -- end tommorow at + same time. + + RETURNS + 0 - OK + EVEX_PARSE_ERROR - fix_fields failed + EVEX_BAD_PARAMS - ENDS before STARTS +*/ + +int +event_timed::init_ends(THD *thd, Item *new_ends) +{ + TIME ltime; + my_time_t my_time_tmp; + my_bool not_used; + + DBUG_ENTER("event_timed::init_ends"); + + if (new_ends->fix_fields(thd, &new_ends)) + DBUG_RETURN(EVEX_PARSE_ERROR); + + // the field was already fixed in init_ends + if ((not_used= new_ends->get_date(<ime, TIME_NO_ZERO_DATE))) + DBUG_RETURN(EVEX_BAD_PARAMS); + + /* + This may result in a 1970-01-01 date if ltime is > 2037-xx-xx + CONVERT_TZ has similar problem + */ + my_tz_UTC->gmt_sec_to_TIME(<ime, TIME_to_timestamp(thd, <ime, ¬_used)); + + if (starts.year && my_time_compare(&starts, <ime) != -1) + DBUG_RETURN(EVEX_BAD_PARAMS); + + ends= ltime; + DBUG_RETURN(0); +} + + +/* + Sets comment. + + SYNOPSIS + event_timed::init_comment() + thd THD - used for memory allocation + comment the string. +*/ + +void +event_timed::init_comment(THD *thd, LEX_STRING *set_comment) +{ + DBUG_ENTER("event_timed::init_comment"); + + comment.str= strmake_root(thd->mem_root, set_comment->str, + comment.length= set_comment->length); + + DBUG_VOID_RETURN; +} + + +/* + Inits definer (definer_user and definer_host) during + parsing. + + SYNOPSIS + event_timed::init_definer() +*/ + +int +event_timed::init_definer(THD *thd) +{ + DBUG_ENTER("event_timed::init_definer"); + + definer_user.str= strdup_root(thd->mem_root, thd->security_ctx->priv_user); + definer_user.length= strlen(thd->security_ctx->priv_user); + + definer_host.str= strdup_root(thd->mem_root, thd->security_ctx->priv_host); + definer_host.length= strlen(thd->security_ctx->priv_host); + + DBUG_RETURN(0); +} + + +/* + Loads an event from a row from mysql.event + + SYNOPSIS + event_timed::load_from_row() + + REMARKS + This method is silent on errors and should behave like that. Callers + should handle throwing of error messages. The reason is that the class + should not know about how to deal with communication. +*/ + +int +event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) +{ + longlong created; + longlong modified; + char *ptr; + event_timed *et; + uint len; + bool res1, res2; + + DBUG_ENTER("event_timed::load_from_row"); + + if (!table) + goto error; + + et= this; + + if (table->s->fields != EVEX_FIELD_COUNT) + goto error; + + if ((et->dbname.str= get_field(mem_root, + table->field[EVEX_FIELD_DB])) == NULL) + goto error; + + et->dbname.length= strlen(et->dbname.str); + + if ((et->name.str= get_field(mem_root, + table->field[EVEX_FIELD_NAME])) == NULL) + goto error; + + et->name.length= strlen(et->name.str); + + if ((et->body.str= get_field(mem_root, + table->field[EVEX_FIELD_BODY])) == NULL) + goto error; + + et->body.length= strlen(et->body.str); + + if ((et->definer.str= get_field(mem_root, + table->field[EVEX_FIELD_DEFINER])) == NullS) + goto error; + et->definer.length= strlen(et->definer.str); + + ptr= strchr(et->definer.str, '@'); + + if (! ptr) + ptr= et->definer.str; + + len= ptr - et->definer.str; + + et->definer_user.str= strmake_root(mem_root, et->definer.str, len); + et->definer_user.length= len; + len= et->definer.length - len - 1; //1 is because of @ + et->definer_host.str= strmake_root(mem_root, ptr + 1, len);//1: because of @ + et->definer_host.length= len; + + + res1= table->field[EVEX_FIELD_STARTS]-> + get_date(&et->starts, TIME_NO_ZERO_DATE); + + res2= table->field[EVEX_FIELD_ENDS]-> + get_date(&et->ends, TIME_NO_ZERO_DATE); + + et->expression= table->field[EVEX_FIELD_INTERVAL_EXPR]->val_int(); + + /* + If res1 and res2 are true then both fields are empty. + Hence if EVEX_FIELD_EXECUTE_AT is empty there is an error. + */ + if (res1 && res2 && !et->expression && table->field[EVEX_FIELD_EXECUTE_AT]-> + get_date(&et->execute_at, TIME_NO_ZERO_DATE)) + goto error; + + /* + In DB the values start from 1 but enum interval_type starts + from 0 + */ + et->interval= (interval_type) + ((ulonglong) table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->val_int() - 1); + + et->modified= table->field[EVEX_FIELD_CREATED]->val_int(); + et->created= table->field[EVEX_FIELD_MODIFIED]->val_int(); + + /* + ToDo Andrey : Ask PeterG & Serg what to do in this case. + Whether on load last_executed_at should be loaded + or it must be 0ed. If last_executed_at is loaded + then an event can be scheduled for execution + instantly. Let's say an event has to be executed + every 15 mins. The server has been stopped for + more than this time and then started. If L_E_AT + is loaded from DB, execution at L_E_AT+15min + will be scheduled. However this time is in the past. + Hence immediate execution. Due to patch of + ::mark_last_executed() last_executed gets time_now + and not execute_at. If not like this a big + queue can be scheduled for times which are still in + the past (2, 3 and more executions which will be + consequent). + */ + set_zero_time(&last_executed, MYSQL_TIMESTAMP_DATETIME); +#ifdef ANDREY_0 + table->field[EVEX_FIELD_LAST_EXECUTED]-> + get_date(&et->last_executed, TIME_NO_ZERO_DATE); +#endif + last_executed_changed= false; + + // ToDo : Andrey . Find a way not to allocate ptr on event_mem_root + if ((ptr= get_field(mem_root, table->field[EVEX_FIELD_STATUS])) == NullS) + goto error; + + DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", et->name.str, ptr)); + et->status= (ptr[0]=='E'? MYSQL_EVENT_ENABLED: + MYSQL_EVENT_DISABLED); + + // ToDo : Andrey . Find a way not to allocate ptr on event_mem_root + if ((ptr= get_field(mem_root, + table->field[EVEX_FIELD_ON_COMPLETION])) == NullS) + goto error; + + et->on_completion= (ptr[0]=='D'? MYSQL_EVENT_ON_COMPLETION_DROP: + MYSQL_EVENT_ON_COMPLETION_PRESERVE); + + et->comment.str= get_field(mem_root, table->field[EVEX_FIELD_COMMENT]); + if (et->comment.str != NullS) + et->comment.length= strlen(et->comment.str); + else + et->comment.length= 0; + + DBUG_RETURN(0); +error: + DBUG_RETURN(EVEX_GET_FIELD_FAILED); +} + + +/* + Note: In the comments this->ends is referenced as m_ends + +*/ + +bool +event_timed::compute_next_execution_time() +{ + TIME time_now; + my_time_t now; + int tmp; + + DBUG_ENTER("event_timed::compute_next_execution_time"); + + if (status == MYSQL_EVENT_DISABLED) + { + DBUG_PRINT("compute_next_execution_time", + ("Event %s is DISABLED", name.str)); + goto ret; + } + //if one-time no need to do computation + if (!expression) + { + //let's check whether it was executed + if (last_executed.year) + { + DBUG_PRINT("compute_next_execution_time", + ("One-time event %s was already executed", name.str)); + if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + { + DBUG_PRINT("compute_next_execution_time", + ("One-time event will be dropped.")); + dropped= true; + } + status= MYSQL_EVENT_DISABLED; + status_changed= true; + } + goto ret; + } + time(&now); + my_tz_UTC->gmt_sec_to_TIME(&time_now, now); +/* + sql_print_information("[%s.%s]", dbname.str, name.str); + sql_print_information("time_now : [%d-%d-%d %d:%d:%d ]", + time_now.year, time_now.month, time_now.day, + time_now.hour, time_now.minute, time_now.second); + sql_print_information("starts : [%d-%d-%d %d:%d:%d ]", starts.year, + starts.month, starts.day, starts.hour, + starts.minute, starts.second); + sql_print_information("ends : [%d-%d-%d %d:%d:%d ]", ends.year, + ends.month, ends.day, ends.hour, + ends.minute, ends.second); + sql_print_information("m_last_ex: [%d-%d-%d %d:%d:%d ]", last_executed.year, + last_executed.month, last_executed.day, + last_executed.hour, last_executed.minute, + last_executed.second); +*/ + //if time_now is after ends don't execute anymore + if (ends.year && (tmp= my_time_compare(&ends, &time_now)) == -1) + { + // time_now is after ends. don't execute anymore + set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); + if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + dropped= true; + status= MYSQL_EVENT_DISABLED; + status_changed= true; + + goto ret; + } + + /* + Here time_now is before or equals ends if the latter is set. + Let's check whether time_now is before starts. + If so schedule for starts + */ + if (starts.year && (tmp= my_time_compare(&time_now, &starts)) < 1) + { + if (tmp == 0 && my_time_compare(&starts, &last_executed) == 0) + { + /* + time_now = starts = last_executed + do nothing or we will schedule for second time execution at starts. + */ + } + else + { + /* + starts is in the future + time_now before starts. Scheduling for starts + */ + execute_at= starts; + goto ret; + } + } + + if (starts.year && ends.year) + { + /* + Both starts and m_ends are set and time_now is between them (incl.) + If last_executed is set then increase with m_expression. The new TIME is + after m_ends set execute_at to 0. And check for on_completion + If not set then schedule for now. + */ + if (!last_executed.year) + execute_at= time_now; + else + { + my_time_t last, ll_ends; + + // There was previous execution + last= sec_since_epoch_TIME(&last_executed) + expression; + ll_ends= sec_since_epoch_TIME(&ends); + //now convert back to TIME + //ToDo Andrey: maybe check for error here? + if (ll_ends < last) + { + // Next execution after ends. No more executions + set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); + if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + dropped= true; + } + else + my_tz_UTC->gmt_sec_to_TIME(&execute_at, last); + } + goto ret; + } + else if (!starts.year && !ends.year) + { + // both starts and m_ends are not set, se we schedule for the next + // based on last_executed + if (!last_executed.year) + //last_executed not set. Schedule the event for now + execute_at= time_now; + else + //ToDo Andrey: maybe check for error here? + my_tz_UTC->gmt_sec_to_TIME(&execute_at, + sec_since_epoch_TIME(&last_executed) + expression); + goto ret; + } + else + { + //either starts or m_ends is set + if (starts.year) + { + /* + - starts is set. + - starts is not in the future according to check made before + Hence schedule for starts + m_expression in case last_executed + is not set, otherwise to last_executed + m_expression + */ + my_time_t last; + + //convert either last_executed or starts to seconds + if (last_executed.year) + last= sec_since_epoch_TIME(&last_executed) + expression; + else + last= sec_since_epoch_TIME(&starts); + + //now convert back to TIME + //ToDo Andrey: maybe check for error here? + my_tz_UTC->gmt_sec_to_TIME(&execute_at, last); + } + else + { + /* + - m_ends is set + - m_ends is after time_now or is equal + Hence check for m_last_execute and increment with m_expression. + If last_executed is not set then schedule for now + */ + my_time_t last, ll_ends; + + if (!last_executed.year) + execute_at= time_now; + else + { + last= sec_since_epoch_TIME(&last_executed); + ll_ends= sec_since_epoch_TIME(&ends); + last+= expression; + //now convert back to TIME + //ToDo Andrey: maybe check for error here? + if (ll_ends < last) + { + set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); + if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) + dropped= true; + } + else + my_tz_UTC->gmt_sec_to_TIME(&execute_at, last); + } + } + goto ret; + } +ret: + + DBUG_RETURN(false); +} + + +void +event_timed::mark_last_executed() +{ + TIME time_now; + my_time_t now; + + time(&now); + my_tz_UTC->gmt_sec_to_TIME(&time_now, now); + + last_executed= time_now; // was execute_at +#ifdef ANDREY_0 + last_executed= execute_at; +#endif + last_executed_changed= true; +} + + +bool +event_timed::drop(THD *thd) +{ + return (bool) evex_drop_event(thd, this, false); +} + + +bool +event_timed::update_fields(THD *thd) +{ + TABLE *table; + Open_tables_state backup; + int ret= 0; + bool opened; + + DBUG_ENTER("event_timed::update_time_fields"); + + DBUG_PRINT("enter", ("name: %*s", name.length, name.str)); + + //no need to update if nothing has changed + if (!(status_changed || last_executed_changed)) + goto done; + + thd->reset_n_backup_open_tables_state(&backup); + + if (!(table= evex_open_event_table(thd, TL_WRITE))) + { + ret= SP_OPEN_TABLE_FAILED; + goto done; + } + + + if ((ret= evex_db_find_event_aux(thd, dbname, name, table))) + goto done; + + store_record(table,record[1]); + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; // Don't update create on row update. + + if (last_executed_changed) + { + table->field[EVEX_FIELD_LAST_EXECUTED]->set_notnull(); + table->field[EVEX_FIELD_LAST_EXECUTED]->store_time(&last_executed, + MYSQL_TIMESTAMP_DATETIME); + last_executed_changed= false; + } + if (status_changed) + { + table->field[EVEX_FIELD_STATUS]->set_notnull(); + table->field[EVEX_FIELD_STATUS]->store((longlong)status); + status_changed= false; + } + + if ((table->file->update_row(table->record[1],table->record[0]))) + ret= EVEX_WRITE_ROW_FAILED; + +done: + close_thread_tables(thd); + thd->restore_backup_open_tables_state(&backup); + + DBUG_RETURN(ret); +} + + +char * +event_timed::get_show_create_event(THD *thd, uint *length) +{ + char *dst, *ret; + uint len, tmp_len; + + len = strlen("CREATE EVENT ") + dbname.length + strlen(".") + name.length + + strlen(" ON SCHEDULE EVERY 5 MINUTE DO ") + body.length + strlen(";"); + + ret= dst= (char*) alloc_root(thd->mem_root, len + 1); + memcpy(dst, "CREATE EVENT ", tmp_len= strlen("CREATE EVENT ")); + dst+= tmp_len; + memcpy(dst, dbname.str, tmp_len=dbname.length); + dst+= tmp_len; + memcpy(dst, ".", tmp_len= strlen(".")); + dst+= tmp_len; + memcpy(dst, name.str, tmp_len= name.length); + dst+= tmp_len; + memcpy(dst, " ON SCHEDULE EVERY 5 MINUTE DO ", + tmp_len= strlen(" ON SCHEDULE EVERY 5 MINUTE DO ")); + dst+= tmp_len; + + memcpy(dst, body.str, tmp_len= body.length); + dst+= tmp_len; + memcpy(dst, ";", 1); + ++dst; + *dst= '\0'; + + *length= len; + + sql_print_information("%d %d[%s]", len, dst-ret, ret); + return ret; +} + + +/* + Executes the event (the underlying sp_head object); + + SYNOPSIS + evex_fill_row() + thd THD + mem_root If != NULL use it to compile the event on it + + Returns + 0 - success + -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) +{ + List<Item> empty_item_list; + int ret= 0; + + DBUG_ENTER("event_timed::execute"); + + VOID(pthread_mutex_lock(&LOCK_running)); + if (running) + { + VOID(pthread_mutex_unlock(&LOCK_running)); + DBUG_RETURN(-100); + } + running= true; + VOID(pthread_mutex_unlock(&LOCK_running)); + + // TODO Andrey : make this as member variable and delete in destructor + empty_item_list.empty(); + + if (!sphead && (ret= compile(thd, mem_root))) + goto done; + + ret= sphead->execute_procedure(thd, &empty_item_list); + + VOID(pthread_mutex_lock(&LOCK_running)); + running= false; + VOID(pthread_mutex_unlock(&LOCK_running)); + +done: + // Don't cache sphead if allocated on another mem_root + if (mem_root && sphead) + { + delete sphead; + sphead= 0; + } + + DBUG_RETURN(ret); +} + + +/* + Returns + 0 - Success + EVEX_COMPILE_ERROR - Error during compilation + +*/ + + +int +event_timed::compile(THD *thd, MEM_ROOT *mem_root) +{ + int ret= 0; + MEM_ROOT *tmp_mem_root= 0; + LEX *old_lex= thd->lex, lex; + char *old_db; + event_timed *ett; + sp_name *spn; + char *old_query; + uint old_query_len; + st_sp_chistics *p; + CHARSET_INFO *old_character_set_client, *old_collation_connection, + *old_character_set_results; + + old_character_set_client= thd->variables.character_set_client; + old_character_set_results= thd->variables.character_set_results; + old_collation_connection= thd->variables.collation_connection; + + thd->variables.character_set_client= + thd->variables.character_set_results= + thd->variables.collation_connection= + get_charset_by_csname("utf8", MY_CS_PRIMARY, MYF(MY_WME)); + + thd->update_charset(); + + DBUG_ENTER("event_timed::compile"); + // change the memory root for the execution time + if (mem_root) + { + tmp_mem_root= thd->mem_root; + thd->mem_root= mem_root; + } + old_query_len= thd->query_length; + old_query= thd->query; + old_db= thd->db; + thd->db= dbname.str; + thd->query= get_show_create_event(thd, &thd->query_length); + DBUG_PRINT("event_timed::compile", ("query:%s",thd->query)); + + thd->lex= &lex; + lex_start(thd, (uchar*)thd->query, thd->query_length); + lex.et_compile_phase= TRUE; + if (yyparse((void *)thd) || thd->is_fatal_error) + { + // Free lex associated resources + // QQ: Do we really need all this stuff here ? + if (lex.sphead) + { + if (&lex != thd->lex) + thd->lex->sphead->restore_lex(thd); + delete lex.sphead; + lex.sphead= 0; + } + // QQ: anything else ? + lex_end(&lex); + thd->lex= old_lex; + + ret= EVEX_COMPILE_ERROR; + goto done; + } + + sphead= lex.sphead; + sphead->m_db= dbname; + //copy also chistics since they will vanish otherwise we get 0x0 pointer + // Todo : Handle sql_mode !! + sphead->set_definer(definer.str, definer.length); + sphead->set_info(0, 0, &lex.sp_chistics, 0/*sql_mode*/); + sphead->optimize(); + ret= 0; +done: + delete lex.et; + lex_end(&lex); + thd->lex= old_lex; + thd->query= old_query; + thd->query_length= old_query_len; + thd->db= old_db; + + thd->variables.character_set_client= old_character_set_client; + thd->variables.character_set_results= old_character_set_results; + thd->variables.collation_connection= old_collation_connection; + thd->update_charset(); + + /* + Change the memory root for the execution time. + */ + if (mem_root) + thd->mem_root= tmp_mem_root; + + DBUG_RETURN(ret); +} + diff --git a/sql/lex.h b/sql/lex.h index e3cbebf4629..41cbae0adea 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -74,6 +74,7 @@ static SYMBOL symbols[] = { { "ASC", SYM(ASC)}, { "ASCII", SYM(ASCII_SYM)}, { "ASENSITIVE", SYM(ASENSITIVE_SYM)}, + { "AT", SYM(AT_SYM)}, { "AUTHORS", SYM(AUTHORS_SYM)}, { "AUTO_INCREMENT", SYM(AUTO_INC)}, { "AVG", SYM(AVG_SYM)}, @@ -121,6 +122,7 @@ static SYMBOL symbols[] = { { "COMMIT", SYM(COMMIT_SYM)}, { "COMMITTED", SYM(COMMITTED_SYM)}, { "COMPACT", SYM(COMPACT_SYM)}, + { "COMPLETION", SYM(COMPLETION_SYM)}, { "COMPRESSED", SYM(COMPRESSED_SYM)}, { "CONCURRENT", SYM(CONCURRENT)}, { "CONDITION", SYM(CONDITION_SYM)}, @@ -180,13 +182,16 @@ static SYMBOL symbols[] = { { "ENABLE", SYM(ENABLE_SYM)}, { "ENCLOSED", SYM(ENCLOSED)}, { "END", SYM(END)}, + { "ENDS", SYM(ENDS_SYM)}, { "ENGINE", SYM(ENGINE_SYM)}, { "ENGINES", SYM(ENGINES_SYM)}, { "ENUM", SYM(ENUM)}, { "ERRORS", SYM(ERRORS)}, { "ESCAPE", SYM(ESCAPE_SYM)}, { "ESCAPED", SYM(ESCAPED)}, + { "EVENT", SYM(EVENT_SYM)}, { "EVENTS", SYM(EVENTS_SYM)}, + { "EVERY", SYM(EVERY_SYM)}, { "EXECUTE", SYM(EXECUTE_SYM)}, { "EXISTS", SYM(EXISTS)}, { "EXIT", SYM(EXIT_SYM)}, @@ -384,6 +389,7 @@ static SYMBOL symbols[] = { { "POLYGON", SYM(POLYGON)}, { "PRECISION", SYM(PRECISION)}, { "PREPARE", SYM(PREPARE_SYM)}, + { "PRESERVE", SYM(PRESERVE_SYM)}, { "PREV", SYM(PREV_SYM)}, { "PRIMARY", SYM(PRIMARY_SYM)}, { "PRIVILEGES", SYM(PRIVILEGES)}, @@ -436,6 +442,7 @@ static SYMBOL symbols[] = { { "ROW_FORMAT", SYM(ROW_FORMAT_SYM)}, { "RTREE", SYM(RTREE_SYM)}, { "SAVEPOINT", SYM(SAVEPOINT_SYM)}, + { "SCHEDULE", SYM(SCHEDULE_SYM)}, { "SCHEMA", SYM(DATABASE)}, { "SCHEMAS", SYM(DATABASES)}, { "SECOND", SYM(SECOND_SYM)}, @@ -484,6 +491,7 @@ static SYMBOL symbols[] = { { "SSL", SYM(SSL_SYM)}, { "START", SYM(START_SYM)}, { "STARTING", SYM(STARTING)}, + { "STARTS", SYM(STARTS_SYM)}, { "STATUS", SYM(STATUS_SYM)}, { "STOP", SYM(STOP_SYM)}, { "STORAGE", SYM(STORAGE_SYM)}, diff --git a/sql/mysqld.cc b/sql/mysqld.cc index f344becffde..f0461106452 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -24,6 +24,7 @@ #include "stacktrace.h" #include "mysqld_suffix.h" #include "mysys_err.h" +#include "event.h" #include "ha_myisam.h" @@ -3505,6 +3506,8 @@ we force server id to 2, but this MySQL server will not act as a slave."); } } + init_events(); + create_shutdown_thread(); create_maintenance_thread(); @@ -3568,6 +3571,7 @@ we force server id to 2, but this MySQL server will not act as a slave."); clean_up(1); wait_for_signal_thread_to_end(); clean_up_mutexes(); + shutdown_events(); my_end(opt_endinfo ? MY_CHECK_ERROR | MY_GIVE_INFO : 0); exit(0); @@ -4529,7 +4533,7 @@ enum options_mysqld OPT_MAX_BINLOG_DUMP_EVENTS, OPT_SPORADIC_BINLOG_DUMP_FAIL, OPT_SAFE_USER_CREATE, OPT_SQL_MODE, OPT_HAVE_NAMED_PIPE, - OPT_DO_PSTACK, OPT_REPORT_HOST, + OPT_DO_PSTACK, OPT_EVENT_EXECUTOR, OPT_REPORT_HOST, OPT_REPORT_USER, OPT_REPORT_PASSWORD, OPT_REPORT_PORT, OPT_SHOW_SLAVE_AUTH_INFO, OPT_SLAVE_LOAD_TMPDIR, OPT_NO_MIX_TYPE, @@ -4807,6 +4811,9 @@ Disable with --skip-bdb (will save memory).", (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}, + {"event-scheduler", OPT_EVENT_EXECUTOR, "Enable/disable the event scheduler.", + (gptr*) &opt_event_executor, (gptr*) &opt_event_executor, 0, GET_BOOL, NO_ARG, + 0/*default*/, 0/*min-value*/, 1/*max-value*/, 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. With this option enabled you can run myisamchk to test (not repair) tables while the MySQL server is running.", @@ -6031,6 +6038,7 @@ struct show_var_st status_vars[]= { {"Bytes_sent", (char*) offsetof(STATUS_VAR, bytes_sent), SHOW_LONG_STATUS}, {"Com_admin_commands", (char*) offsetof(STATUS_VAR, com_other), SHOW_LONG_STATUS}, {"Com_alter_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_DB]), SHOW_LONG_STATUS}, + {"Com_alter_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_EVENT]), SHOW_LONG_STATUS}, {"Com_alter_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_TABLE]), SHOW_LONG_STATUS}, {"Com_analyze", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ANALYZE]), SHOW_LONG_STATUS}, {"Com_backup_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_BACKUP_TABLE]), SHOW_LONG_STATUS}, @@ -6041,6 +6049,7 @@ struct show_var_st status_vars[]= { {"Com_checksum", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHECKSUM]), SHOW_LONG_STATUS}, {"Com_commit", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_COMMIT]), SHOW_LONG_STATUS}, {"Com_create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_DB]), SHOW_LONG_STATUS}, + {"Com_create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_EVENT]), SHOW_LONG_STATUS}, {"Com_create_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_FUNCTION]), SHOW_LONG_STATUS}, {"Com_create_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_INDEX]), SHOW_LONG_STATUS}, {"Com_create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_TABLE]), SHOW_LONG_STATUS}, @@ -6049,6 +6058,7 @@ struct show_var_st status_vars[]= { {"Com_delete_multi", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DELETE_MULTI]), SHOW_LONG_STATUS}, {"Com_do", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DO]), SHOW_LONG_STATUS}, {"Com_drop_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_DB]), SHOW_LONG_STATUS}, + {"Com_drop_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_EVENT]), SHOW_LONG_STATUS}, {"Com_drop_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_FUNCTION]), SHOW_LONG_STATUS}, {"Com_drop_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_INDEX]), SHOW_LONG_STATUS}, {"Com_drop_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_TABLE]), SHOW_LONG_STATUS}, @@ -6090,6 +6100,7 @@ struct show_var_st status_vars[]= { {"Com_show_collations", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_COLLATIONS]), SHOW_LONG_STATUS}, {"Com_show_column_types", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_COLUMN_TYPES]), SHOW_LONG_STATUS}, {"Com_show_create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_DB]), SHOW_LONG_STATUS}, + {"Com_show_create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_EVENT]), SHOW_LONG_STATUS}, {"Com_show_create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE]), SHOW_LONG_STATUS}, {"Com_show_databases", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_DATABASES]), SHOW_LONG_STATUS}, {"Com_show_engine_logs", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_LOGS]), SHOW_LONG_STATUS}, diff --git a/sql/set_var.cc b/sql/set_var.cc index 19dc2265347..34942390170 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -104,6 +104,7 @@ extern ulong ndb_cache_check_time; +extern my_bool event_executor_running_global_var; static HASH system_variable_hash; const char *bool_type_names[]= { "OFF", "ON", NullS }; @@ -208,6 +209,8 @@ sys_var_long_ptr sys_delayed_insert_timeout("delayed_insert_timeout", &delayed_insert_timeout); sys_var_long_ptr sys_delayed_queue_size("delayed_queue_size", &delayed_queue_size); +sys_var_event_executor sys_event_executor("event_scheduler", + &event_executor_running_global_var); sys_var_long_ptr sys_expire_logs_days("expire_logs_days", &expire_logs_days); sys_var_bool_ptr sys_flush("flush", &myisam_flush); @@ -666,6 +669,7 @@ struct show_var_st init_vars[]= { {sys_div_precincrement.name,(char*) &sys_div_precincrement,SHOW_SYS}, {sys_engine_condition_pushdown.name, (char*) &sys_engine_condition_pushdown, SHOW_SYS}, + {sys_event_executor.name, (char*) &sys_event_executor, SHOW_SYS}, {sys_expire_logs_days.name, (char*) &sys_expire_logs_days, SHOW_SYS}, {sys_flush.name, (char*) &sys_flush, SHOW_SYS}, {sys_flush_time.name, (char*) &sys_flush_time, SHOW_SYS}, @@ -3362,6 +3366,7 @@ bool sys_var_trust_routine_creators::update(THD *thd, set_var *var) return sys_var_bool_ptr::update(thd, var); } + /**************************************************************************** Used templates ****************************************************************************/ diff --git a/sql/set_var.h b/sql/set_var.h index 14059f7e9b7..7d2a7999ddc 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -782,6 +782,17 @@ public: bool update(THD *thd, set_var *var); }; + +class sys_var_event_executor :public sys_var_bool_ptr +{ + /* We need a derived class only to have a warn_deprecated() */ +public: + sys_var_event_executor(const char *name_arg, my_bool *value_arg) : + sys_var_bool_ptr(name_arg, value_arg) {}; + bool update(THD *thd, set_var *var); +}; + + /**************************************************************************** Classes for parsing of the SET command ****************************************************************************/ diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index f556ad9db65..abd4b746aa8 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5721,3 +5721,33 @@ ER_DROP_PARTITION_WHEN_FK_DEFINED swe "Kan inte ta bort en partition när en främmande nyckel är definierad på tabellen" ER_PLUGIN_IS_NOT_LOADED eng "Plugin '%-.64s' is not loaded" +ER_EVENT_ALREADY_EXISTS + eng "Event %s already exists" +ER_EVENT_STORE_FAILED + eng "Failed to store event %s. Error code %d from storage engine." +ER_EVENT_DOES_NOT_EXIST + eng "Event %s does not exist" +ER_EVENT_CANT_ALTER + eng "Failed to alter event %s" +ER_EVENT_DROP_FAILED + eng "Failed to drop %s" +ER_EVENT_INTERVAL_NOT_POSITIVE + eng "INTERVAL must be positive" +ER_EVENT_ENDS_BEFORE_STARTS + eng "ENDS must be after STARTS" +ER_EVENT_EXEC_TIME_IN_THE_PAST + eng "Activation (AT) time is in the past" +ER_EVENT_OPEN_TABLE_FAILED + eng "Failed to open mysql.event" +ER_EVENT_NEITHER_M_EXPR_NOR_M_AT + eng "No datetime expression provided" +ER_EVENT_COL_COUNT_DOESNT_MATCH + eng "Column count of %s.%s is wrong. Table probably corrupted" +ER_EVENT_CANNOT_LOAD_FROM_TABLE + eng "Cannot load from mysql.event. Table probably corrupted" +ER_EVENT_CANNOT_DELETE + eng "Failed to delete the event from mysql.event" +ER_EVENT_COMPILE_ERROR + eng "Error during compilation of event's body" +ER_EVENT_SAME_NAME + eng "Same old and new event name" diff --git a/sql/sp_head.h b/sql/sp_head.h index 90ce53db9db..b928330e82d 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -130,7 +130,6 @@ public: uint m_returns_len; // For FUNCTIONs only uint m_returns_pack; // For FUNCTIONs only const uchar *m_tmp_query; // Temporary pointer to sub query string - uint m_old_cmq; // Old CLIENT_MULTI_QUERIES value st_sp_chistics *m_chistics; ulong m_sql_mode; // For SHOW CREATE and execution LEX_STRING m_qname; // db.name diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 59f1e91e2e5..8613bd6b503 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -352,6 +352,14 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) if (table->s->fields <= 36 && (user.access & GRANT_ACL)) user.access|= CREATE_USER_ACL; + + /* + if it is pre 5.1.4 privilege table then map CREATE privilege on + CREATE|ALTER|DROP|EXECUTE EVENT + */ + if (table->s->fields <= 37 && (user.access & CREATE_ACL)) + user.access|= EVENT_ACL; + user.sort= get_sort(2,user.host.hostname,user.user); user.hostname_length= (user.host.hostname ? (uint) strlen(user.host.hostname) : 0); @@ -3971,13 +3979,13 @@ static const char *command_array[]= "ALTER", "SHOW DATABASES", "SUPER", "CREATE TEMPORARY TABLES", "LOCK TABLES", "EXECUTE", "REPLICATION SLAVE", "REPLICATION CLIENT", "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", "ALTER ROUTINE", - "CREATE USER" + "CREATE USER", "EVENT" }; static uint command_lengths[]= { 6, 6, 6, 6, 6, 4, 6, 8, 7, 4, 5, 10, 5, 5, 14, 5, 23, 11, 7, 17, 18, 11, 9, - 14, 13, 11 + 14, 13, 11, 5 }; diff --git a/sql/sql_acl.h b/sql/sql_acl.h index 0a9c6ba785f..44e42b961a2 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -42,6 +42,7 @@ #define CREATE_PROC_ACL (1L << 23) #define ALTER_PROC_ACL (1L << 24) #define CREATE_USER_ACL (1L << 25) +#define EVENT_ACL (1L << 26) /* don't forget to update 1. static struct show_privileges_st sys_privileges[] @@ -56,7 +57,7 @@ (UPDATE_ACL | SELECT_ACL | INSERT_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \ GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL | \ LOCK_TABLES_ACL | EXECUTE_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL | \ - CREATE_PROC_ACL | ALTER_PROC_ACL) + CREATE_PROC_ACL | ALTER_PROC_ACL | EVENT_ACL) #define TABLE_ACLS \ (SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \ @@ -78,7 +79,7 @@ REFERENCES_ACL | INDEX_ACL | ALTER_ACL | SHOW_DB_ACL | SUPER_ACL | \ CREATE_TMP_ACL | LOCK_TABLES_ACL | REPL_SLAVE_ACL | REPL_CLIENT_ACL | \ EXECUTE_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL | CREATE_PROC_ACL | \ - ALTER_PROC_ACL | CREATE_USER_ACL) + ALTER_PROC_ACL | CREATE_USER_ACL | EVENT_ACL) #define DEFAULT_CREATE_PROC_ACLS \ (ALTER_PROC_ACL | EXECUTE_ACL) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 4d32e26f1b7..f2f065510da 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -177,7 +177,9 @@ void lex_start(THD *thd, const uchar *buf, uint length) lex->spcont= NULL; lex->proc_list.first= 0; lex->query_tables_own_last= 0; - lex->escape_used= FALSE; + lex->escape_used= lex->et_compile_phase= FALSE; + + lex->et= NULL; if (lex->sroutines.records) my_hash_reset(&lex->sroutines); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d7ad28a95f5..fdcd785f920 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -26,6 +26,7 @@ class sp_name; class sp_instr; class sp_pcontext; class partition_info; +class event_timed; /* The following hack is needed because mysql_yacc.cc does not define @@ -94,6 +95,8 @@ enum enum_sql_command { SQLCOM_SHOW_PROC_CODE, SQLCOM_SHOW_FUNC_CODE, SQLCOM_INSTALL_PLUGIN, SQLCOM_UNINSTALL_PLUGIN, SQLCOM_SHOW_AUTHORS, + SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT, + SQLCOM_SHOW_CREATE_EVENT, /* This should be the last !!! */ SQLCOM_END @@ -890,6 +893,10 @@ typedef struct st_lex uint sroutines_list_own_elements; st_sp_chistics sp_chistics; + + event_timed *et; + bool et_compile_phase; + bool only_view; /* used for SHOW CREATE TABLE/VIEW */ /* field_list was created for view and should be removed before PS/SP diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index f6e94c118af..3a729ddd9fa 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -25,6 +25,7 @@ #include "sp_head.h" #include "sp.h" #include "sp_cache.h" +#include "event.h" #ifdef HAVE_OPENSSL /* @@ -642,6 +643,9 @@ void init_update_queries(void) uc_update_queries[SQLCOM_DROP_INDEX]=1; uc_update_queries[SQLCOM_CREATE_VIEW]=1; uc_update_queries[SQLCOM_DROP_VIEW]=1; + uc_update_queries[SQLCOM_CREATE_EVENT]=1; + uc_update_queries[SQLCOM_ALTER_EVENT]=1; + uc_update_queries[SQLCOM_DROP_EVENT]=1; } bool is_update_query(enum enum_sql_command command) @@ -3667,6 +3671,60 @@ end_with_restore_list: res=mysqld_show_create_db(thd,lex->name,&lex->create_info); break; } + case SQLCOM_CREATE_EVENT: + case SQLCOM_ALTER_EVENT: + case SQLCOM_DROP_EVENT: + { + DBUG_ASSERT(lex->et); + do { + if (! lex->et->dbname.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))) + break; + + switch (lex->sql_command) { + case SQLCOM_CREATE_EVENT: + res= evex_create_event(thd, lex->et, (uint) lex->create_info.options); + break; + case SQLCOM_ALTER_EVENT: + res= evex_update_event(thd, lex->et, lex->spname); + break; + case SQLCOM_DROP_EVENT: + evex_drop_event(thd, lex->et, lex->drop_if_exists); + default:; + } + if (!res) + send_ok(thd, 1); + + /* lex->unit.cleanup() is called outside, no need to call it here */ + } while (0); + delete lex->et; + delete lex->sphead; + lex->et= 0; + lex->sphead= 0; + break; + } + case SQLCOM_SHOW_CREATE_EVENT: + { + if (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0, + is_schema_db(lex->spname->m_db.str))) + break; + + if (lex->spname->m_name.length > NAME_LEN) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str); + goto error; + } + /* TODO : Implement it */ + send_ok(thd, 1); + break; + } case SQLCOM_CREATE_FUNCTION: // UDF function { if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0)) @@ -5598,6 +5656,11 @@ void mysql_parse(THD *thd, char *inBuf, uint length) delete thd->lex->sphead; thd->lex->sphead= NULL; } + if (thd->lex->et) + { + delete thd->lex->et; + thd->lex->et= NULL; + } } else { @@ -5633,6 +5696,11 @@ void mysql_parse(THD *thd, char *inBuf, uint length) delete thd->lex->sphead; thd->lex->sphead= NULL; } + if (thd->lex->et) + { + delete thd->lex->et; + thd->lex->et= NULL; + } } thd->proc_info="freeing items"; thd->end_statement(); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index badc15f2ab5..783fad41cf1 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -147,6 +147,7 @@ static struct show_privileges_st sys_privileges[]= {"Create user", "Server Admin", "To create new users"}, {"Delete", "Tables", "To delete existing rows"}, {"Drop", "Databases,Tables", "To drop databases, tables, and views"}, + {"Event","Server Admin","Creation, alteration, deletion and execution of events."}, {"Execute", "Functions,Procedures", "To execute stored routines"}, {"File", "File access on server", "To read and write files on the server"}, {"Grant option", "Databases,Tables,Functions,Procedures", "To give to other users those privileges you possess"}, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c79ad97be4e..4b3e694b911 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -38,6 +38,7 @@ #include "sp_pcontext.h" #include "sp_rcontext.h" #include "sp.h" +#include "event.h" #include <myisam.h> #include <myisammrg.h> @@ -136,6 +137,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token ASC %token ASCII_SYM %token ASENSITIVE_SYM +%token AT_SYM %token ATAN %token AUTHORS_SYM %token AUTO_INC @@ -186,6 +188,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token COMMITTED_SYM %token COMMIT_SYM %token COMPACT_SYM +%token COMPLETION_SYM %token COMPRESSED_SYM %token CONCAT %token CONCAT_WS @@ -254,6 +257,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token ENCODE_SYM %token ENCRYPT %token END +%token ENDS_SYM %token ENGINES_SYM %token ENGINE_SYM %token ENUM @@ -262,7 +266,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token ERRORS %token ESCAPED %token ESCAPE_SYM +%token EVENT_SYM %token EVENTS_SYM +%token EVERY_SYM %token EXECUTE_SYM %token EXISTS %token EXIT_SYM @@ -488,6 +494,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token POSITION_SYM %token PRECISION %token PREPARE_SYM +%token PRESERVE_SYM %token PREV_SYM %token PRIMARY_SYM %token PRIVILEGES @@ -544,6 +551,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token ROW_SYM %token RTREE_SYM %token SAVEPOINT_SYM +%token SCHEDULE_SYM %token SECOND_MICROSECOND_SYM %token SECOND_SYM %token SECURITY_SYM @@ -583,6 +591,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token SSL_SYM %token STARTING %token START_SYM +%token STARTS_SYM %token STATUS_SYM %token STD_SYM %token STDDEV_SAMP_SYM @@ -676,6 +685,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token YEAR_SYM %token ZEROFILL + %left JOIN_SYM INNER_SYM STRAIGHT_JOIN CROSS LEFT RIGHT /* A dummy token to force the priority of table_ref production in a join. */ %left TABLE_REF_PRIORITY @@ -857,6 +867,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); END_OF_INPUT %type <NONE> call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt +%type <NONE> sp_proc_stmt_statement sp_proc_stmt_return +%type <NONE> sp_proc_stmt_if sp_proc_stmt_case_simple sp_proc_stmt_case +%type <NONE> sp_labeled_control sp_proc_stmt_unlabeled sp_proc_stmt_leave +%type <NONE> sp_proc_stmt_iterate sp_proc_stmt_label sp_proc_stmt_goto +%type <NONE> sp_proc_stmt_open sp_proc_stmt_fetch sp_proc_stmt_close + %type <num> sp_decl_idents sp_opt_inout sp_handler_type sp_hcond_list %type <spcondtype> sp_cond sp_hcond %type <spblock> sp_decls sp_decl @@ -1245,7 +1261,7 @@ create: * stored procedure, otherwise yylex will chop it into pieces * at each ';'. */ - sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; + $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES); } '(' @@ -1278,9 +1294,9 @@ create: YYABORT; sp->init_strings(YYTHD, lex, $3); lex->sql_command= SQLCOM_CREATE_PROCEDURE; + /* Restore flag if it was cleared above */ - if (sp->m_old_cmq) - YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES; + YYTHD->client_capabilities |= $<ulong_num>3; sp->restore_thd_mem_root(YYTHD); } | CREATE @@ -1295,7 +1311,228 @@ create: { Lex->sql_command = SQLCOM_CREATE_USER; } - ; + | CREATE 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; + + if (!(lex->et= new event_timed())) // implicitly calls event_timed::init() + YYABORT; + + /* + We have to turn of 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); + + if (!lex->et_compile_phase) + lex->et->init_name(YYTHD, $4); + + lex->sphead= 0;//defensive programming + } + ON SCHEDULE_SYM ev_schedule_time + ev_on_completion + ev_status + ev_comment + DO_SYM ev_sql_stmt + { + /* + Restore flag if it was cleared above + $1 - CREATE + $2 - EVENT_SYM + $3 - opt_if_not_exists + $4 - sp_name + $5 - the block above + */ + YYTHD->client_capabilities |= $<ulong_num>5; + + /* + sql_command is set here because some rules in ev_sql_stmt + can overwrite it + */ + Lex->sql_command= SQLCOM_CREATE_EVENT; + } + ; + +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, MYF(0)); + YYABORT; + break; + } + } + } + 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 EVEX_BAD_PARAMS: + my_error(ER_EVENT_EXEC_TIME_IN_THE_PAST, MYF(0)); + YYABORT; + break; + } + } + } + ; + +ev_status: /* empty */ + | ENABLE_SYM + { + LEX *lex=Lex; + if (!lex->et_compile_phase) + lex->et->status= MYSQL_EVENT_ENABLED; + } + | DISABLE_SYM + { + LEX *lex=Lex; + + if (!lex->et_compile_phase) + lex->et->status= MYSQL_EVENT_DISABLED; + } + ; +ev_starts: /* empty */ + | STARTS_SYM expr + { + LEX *lex= Lex; + if (!lex->et_compile_phase) + lex->et->init_starts(YYTHD, $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; + } + } + } + ; +ev_on_completion: /* empty */ + | ON COMPLETION_SYM PRESERVE_SYM + { + LEX *lex=Lex; + if (!lex->et_compile_phase) + lex->et->on_completion= MYSQL_EVENT_ON_COMPLETION_PRESERVE; + } + | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM + { + LEX *lex=Lex; + if (!lex->et_compile_phase) + lex->et->on_completion= MYSQL_EVENT_ON_COMPLETION_DROP; + } + ; +ev_comment: /* empty */ + | COMMENT_SYM TEXT_STRING_sys + { + LEX *lex= Lex; + if (!lex->et_compile_phase) + { + lex->comment= $2; + lex->et->init_comment(YYTHD, &$2); + } + } + ; + +ev_sql_stmt: + { + LEX *lex= Lex; + sp_head *sp; + + if (!(sp= new sp_head())) + YYABORT; + + sp->reset_thd_mem_root(YYTHD); + sp->init(lex); + + sp->m_type= TYPE_ENUM_PROCEDURE; + lex->sphead= sp; + + bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); + lex->sphead->m_chistics= &lex->sp_chistics; + + lex->sphead->m_body_begin= lex->ptr; + if (!lex->et_compile_phase) + lex->et->body_begin= lex->ptr; + } + ev_sql_stmt_inner + { + LEX *lex=Lex; + sp_head *sp= lex->sphead; + // return back to the original memory root ASAP + sp->init_strings(YYTHD, lex, NULL); + sp->restore_thd_mem_root(YYTHD); + + lex->sp_chistics.suid= SP_IS_SUID;//always the definer! + + if (!lex->et_compile_phase) + { + lex->et->init_body(YYTHD); + lex->et->init_definer(YYTHD); + } + } + ; + +ev_sql_stmt_inner: + sp_proc_stmt_statement + | sp_proc_stmt_return + | sp_proc_stmt_if + | sp_proc_stmt_case_simple + | sp_proc_stmt_case + | sp_labeled_control {} + | sp_proc_stmt_unlabeled + | sp_proc_stmt_leave + | sp_proc_stmt_iterate + | sp_proc_stmt_label + | sp_proc_stmt_goto + | sp_proc_stmt_open + | sp_proc_stmt_fetch + | sp_proc_stmt_close + ; clear_privileges: /* Nothing */ @@ -1355,7 +1592,7 @@ create_function_tail: * stored procedure, otherwise yylex will chop it into pieces * at each ';'. */ - sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; + $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; lex->sphead->m_param_begin= lex->tok_start+1; } @@ -1434,9 +1671,9 @@ create_function_tail: YYABORT; lex->sql_command= SQLCOM_CREATE_SPFUNCTION; sp->init_strings(YYTHD, lex, lex->spname); + /* Restore flag if it was cleared above */ - if (sp->m_old_cmq) - YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES; + YYTHD->client_capabilities |= $<ulong_num>2; sp->restore_thd_mem_root(YYTHD); } ; @@ -1905,6 +2142,28 @@ sp_opt_default: ; sp_proc_stmt: + sp_proc_stmt_statement + | sp_proc_stmt_return + | sp_proc_stmt_if + | sp_proc_stmt_case_simple + | sp_proc_stmt_case + | sp_labeled_control + {} + | sp_proc_stmt_unlabeled + | sp_proc_stmt_leave + | sp_proc_stmt_iterate + | sp_proc_stmt_label + | sp_proc_stmt_goto + | sp_proc_stmt_open + | sp_proc_stmt_fetch + | sp_proc_stmt_close + ; + +sp_proc_stmt_if: + IF sp_if END IF {} + ; + +sp_proc_stmt_statement: { LEX *lex= Lex; @@ -1947,7 +2206,10 @@ sp_proc_stmt: } sp->restore_lex(YYTHD); } - | RETURN_SYM + ; + +sp_proc_stmt_return: + RETURN_SYM { Lex->sphead->reset_lex(YYTHD); } expr { @@ -1970,13 +2232,18 @@ sp_proc_stmt: } sp->restore_lex(YYTHD); } - | IF sp_if END IF {} - | CASE_SYM WHEN_SYM + ; + +sp_proc_stmt_case_simple: + CASE_SYM WHEN_SYM { Lex->sphead->m_flags&= ~sp_head::IN_SIMPLE_CASE; } sp_case END CASE_SYM {} - | CASE_SYM + ; + +sp_proc_stmt_case: + CASE_SYM { Lex->sphead->reset_lex(YYTHD); } expr WHEN_SYM { @@ -2000,9 +2267,10 @@ sp_proc_stmt: { Lex->spcont->pop_pvar(); } - | sp_labeled_control - {} - | { /* Unlabeled controls get a secret label. */ + ; + +sp_proc_stmt_unlabeled: + { /* Unlabeled controls get a secret label. */ LEX *lex= Lex; lex->spcont->push_label((char *)"", lex->sphead->instructions()); @@ -2013,7 +2281,10 @@ sp_proc_stmt: lex->sphead->backpatch(lex->spcont->pop_label()); } - | LEAVE_SYM label_ident + ; + +sp_proc_stmt_leave: + LEAVE_SYM label_ident { LEX *lex= Lex; sp_head *sp = lex->sphead; @@ -2043,7 +2314,10 @@ sp_proc_stmt: sp->add_instr(i); } } - | ITERATE_SYM label_ident + ; + +sp_proc_stmt_iterate: + ITERATE_SYM label_ident { LEX *lex= Lex; sp_head *sp= lex->sphead; @@ -2071,7 +2345,10 @@ sp_proc_stmt: sp->add_instr(i); } } - | LABEL_SYM IDENT + ; + +sp_proc_stmt_label: + LABEL_SYM IDENT { #ifdef SP_GOTO LEX *lex= Lex; @@ -2096,7 +2373,10 @@ sp_proc_stmt: YYABORT; #endif } - | GOTO_SYM IDENT + ; + +sp_proc_stmt_goto: + GOTO_SYM IDENT { #ifdef SP_GOTO LEX *lex= Lex; @@ -2156,7 +2436,10 @@ sp_proc_stmt: YYABORT; #endif } - | OPEN_SYM ident + ; + +sp_proc_stmt_open: + OPEN_SYM ident { LEX *lex= Lex; sp_head *sp= lex->sphead; @@ -2171,7 +2454,10 @@ sp_proc_stmt: i= new sp_instr_copen(sp->instructions(), lex->spcont, offset); sp->add_instr(i); } - | FETCH_SYM sp_opt_fetch_noise ident INTO + ; + +sp_proc_stmt_fetch: + FETCH_SYM sp_opt_fetch_noise ident INTO { LEX *lex= Lex; sp_head *sp= lex->sphead; @@ -2188,7 +2474,10 @@ sp_proc_stmt: } sp_fetch_list { } - | CLOSE_SYM ident + ; + +sp_proc_stmt_close: + CLOSE_SYM ident { LEX *lex= Lex; sp_head *sp= lex->sphead; @@ -3913,8 +4202,83 @@ alter: } view_list_opt AS view_select view_check_option {} - ; + | 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 + */ + { + 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. + */ + // ToDo Andrey : Change the error message + my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); + YYABORT; + } + lex->spname= 0;//defensive programming + + et= new event_timed();// implicitly calls event_timed::init() + lex->et = et; + et->init_name(YYTHD, $3); + + /* + We have to turn of CLIENT_MULTI_QUERIES while parsing a + stored procedure, otherwise yylex will chop it into pieces + at each ';'. + */ + $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; + YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; + + /* + defensive. in sql_parse.cc it is checked whether is not null + and then deleted + */ + lex->sphead= 0; + } + ev_on_schedule + ev_rename_to + ev_on_completion + ev_status + ev_comment + ev_opt_sql_stmt + { + /* + $1 - ALTER + $2 - EVENT_SYM + $3 - sp_name + $4 - the block above + */ + 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_ALTER_EVENT; + } + ; +ev_on_schedule: /* empty */ + | ON SCHEDULE_SYM ev_schedule_time; + +ev_opt_sql_stmt: /* empty*/ + | DO_SYM ev_sql_stmt; + +ev_rename_to: /* empty */ + | 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 + } + ; + ident_or_empty: /* empty */ { $$= 0; } | ident { $$= $1.str; }; @@ -6620,7 +6984,29 @@ drop: lex->sql_command= SQLCOM_DROP_TRIGGER; lex->spname= $3; } - ; + | DROP EVENT_SYM if_exists sp_name + { + LEX *lex=Lex; + + if (lex->et) + { + // ToDo Andrey : Change the error message + /* + 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 event_timed())) + YYABORT; + lex->et->init_name(YYTHD, $4); + + lex->sql_command = SQLCOM_DROP_EVENT; + lex->drop_if_exists= $3; + } + ; table_list: table_name @@ -7272,7 +7658,12 @@ show_param: Lex->spname= $3; #endif } - ; + | CREATE EVENT_SYM sp_name + { + Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; + Lex->spname= $3; + }; + ; show_engine_param: STATUS_SYM @@ -8159,6 +8550,7 @@ keyword_sp: | AGGREGATE_SYM {} | ALGORITHM_SYM {} | ANY_SYM {} + | AT_SYM {} | AUTO_INC {} | AVG_ROW_LENGTH {} | AVG_SYM {} @@ -8179,6 +8571,7 @@ keyword_sp: | COLUMNS {} | COMMITTED_SYM {} | COMPACT_SYM {} + | COMPLETION_SYM {} | COMPRESSED_SYM {} | CONCURRENT {} | CONSISTENT_SYM {} @@ -8195,13 +8588,16 @@ keyword_sp: | DUMPFILE {} | DUPLICATE_SYM {} | DYNAMIC_SYM {} + | ENDS_SYM {} | ENUM {} | ENGINE_SYM {} | ENGINES_SYM {} | ERRORS {} | ESCAPE_SYM {} + | EVENT_SYM {} | EVENTS_SYM {} - | EXPANSION_SYM {} + | EVERY_SYM {} + | EXPANSION_SYM {} | EXTENDED_SYM {} | FAST_SYM {} | FOUND_SYM {} @@ -8293,6 +8689,7 @@ keyword_sp: | PHASE_SYM {} | POINT_SYM {} | POLYGON {} + | PRESERVE_SYM {} | PREV_SYM {} | PRIVILEGES {} | PROCESS {} @@ -8322,6 +8719,7 @@ keyword_sp: | ROW_FORMAT_SYM {} | ROW_SYM {} | RTREE_SYM {} + | SCHEDULE_SYM {} | SECOND_SYM {} | SERIAL_SYM {} | SERIALIZABLE_SYM {} @@ -8335,6 +8733,7 @@ keyword_sp: | SQL_BUFFER_RESULT {} | SQL_NO_CACHE_SYM {} | SQL_THREAD {} + | STARTS_SYM {} | STATUS_SYM {} | STORAGE_SYM {} | STRING_SYM {} @@ -9061,6 +9460,7 @@ object_privilege: | CREATE ROUTINE_SYM { Lex->grant |= CREATE_PROC_ACL; } | ALTER ROUTINE_SYM { Lex->grant |= ALTER_PROC_ACL; } | CREATE USER { Lex->grant |= CREATE_USER_ACL; } + | EVENT_SYM { Lex->grant |= EVENT_ACL;} ; @@ -9711,7 +10111,7 @@ trigger_tail: stored procedure, otherwise yylex will chop it into pieces at each ';'. */ - sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; + $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); @@ -9726,8 +10126,8 @@ trigger_tail: lex->sql_command= SQLCOM_CREATE_TRIGGER; sp->init_strings(YYTHD, lex, $3); /* Restore flag if it was cleared above */ - if (sp->m_old_cmq) - YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES; + + YYTHD->client_capabilities |= $<ulong_num>11; sp->restore_thd_mem_root(YYTHD); if (sp->is_not_allowed_in_function("trigger")) diff --git a/sql/table.cc b/sql/table.cc index ff29a33ef03..e4f47ccf1eb 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -282,7 +282,8 @@ int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags) */ if (share->db.length == 5 && !my_strcasecmp(system_charset_info, share->db.str, "mysql") && - !my_strcasecmp(system_charset_info, share->table_name.str, "proc")) + (!my_strcasecmp(system_charset_info, share->table_name.str, "proc") || + !my_strcasecmp(system_charset_info, share->table_name.str, "event"))) share->system_table= 1; error_given= 1; } diff --git a/sql/tztime.cc b/sql/tztime.cc index eba2f8f4a7b..50c3cf1fe4f 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -807,6 +807,18 @@ sec_since_epoch(int year, int mon, int mday, int hour, int min ,int sec) } + /* + Works like sec_since_epoch but expects TIME structure as parameter. +*/ + +my_time_t +sec_since_epoch_TIME(TIME *t) +{ + return sec_since_epoch(t->year, t->month, t->day, + t->hour, t->minute, t->second); +} + + /* Converts local time in broken down TIME representation to my_time_t representation. diff --git a/sql/tztime.h b/sql/tztime.h index a168fe4fb73..2d574eadf18 100644 --- a/sql/tztime.h +++ b/sql/tztime.h @@ -64,6 +64,7 @@ extern Time_zone * my_tz_find(const String *name, TABLE_LIST *tz_tables); extern Time_zone * my_tz_find_with_opening_tz_tables(THD *thd, const String *name); extern my_bool my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap); extern void my_tz_free(); +extern my_time_t sec_since_epoch_TIME(TIME *t); extern TABLE_LIST fake_time_zone_tables_list; |