summaryrefslogtreecommitdiff
path: root/sql/event.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/event.cc')
-rw-r--r--sql/event.cc909
1 files changed, 909 insertions, 0 deletions
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);
+}
+