Commit 7120d43c authored by andrey@lmy004's avatar andrey@lmy004

WL#1034 updated

- split into several files
- forbid parallel execution (before analyse is done how to make it possible)
  because the same sp_head instance cannot be executed in parallel
- added GPL headers
- changed EVENT_ACL to be per DB variable
- fixed minor problems
parent 96f9681c
......@@ -89,6 +89,7 @@ CREATE TABLE user (
Create_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
Alter_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
Create_user_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
ssl_type enum('','ANY','X509', 'SPECIFIED') COLLATE utf8_general_ci DEFAULT '' NOT NULL,
ssl_cipher BLOB NOT NULL,
x509_issuer BLOB NOT NULL,
......@@ -103,9 +104,9 @@ CHARACTER SET utf8 COLLATE utf8_bin
comment='Users and global privileges';
INSERT INTO user VALUES ('localhost' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user VALUES ('@HOSTNAME@%' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
REPLACE INTO user VALUES ('127.0.0.1' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user VALUES ('localhost' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user VALUES ('@HOSTNAME@%' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
REPLACE INTO user VALUES ('127.0.0.1' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user (host,user) VALUES ('localhost','');
INSERT INTO user (host,user) VALUES ('@HOSTNAME@%','');
......@@ -566,3 +567,29 @@ CREATE TABLE proc (
comment char(64) collate utf8_bin DEFAULT '' NOT NULL,
PRIMARY KEY (db,name,type)
) character set utf8 comment='Stored Procedures';
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 default '0000-00-00 00:00:00',
'modified' TIMESTAMP NOT NULL default '0000-00-00 00:00:00',
'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 COMMENT 'Events';
......@@ -526,3 +526,41 @@ ALTER TABLE proc MODIFY db
char(77) collate utf8_bin DEFAULT '' NOT NULL,
MODIFY comment
char(64) collate utf8_bin DEFAULT '' NOT NULL;
#
# EVENT table
#
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 default '0000-00-00 00:00:00',
'modified' TIMESTAMP NOT NULL default '0000-00-00 00:00:00',
'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 COMMENT 'Events';
#
# EVENT privilege
#
ALTER TABLE mysql.user add Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL AFTER Create_user_priv;
ALTER TABLE mysql.db add Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL;
......@@ -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 event.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 \
......@@ -94,7 +94,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
gstream.cc spatial.cc sql_help.cc sql_cursor.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.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 \
......
/* Copyright (C) 2000-2003 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include "mysql_priv.h"
//#include "sql_acl.h"
#include "event.h"
#include "event_priv.h"
#include "sp.h"
/*
TODO list :
1. Move event_timed class to event_timed.cc as well as the part of the header
2. Do something aboute the events replication. SQL statements issued while
executing an event should not be logged into the binary log.
3. Add a lock and use it for guarding access to events_array dynamic array.
4. Add checks everywhere where new instance of THD is created. NULL can be
- Remove m_ prefixes of member variables.
- Use timestamps instead of datetime.
- Don't use SP's functionality for opening and closing of tables
- CREATE EVENT should not go into binary log! Does it now? The SQL statements
issued by the EVENT are replicated.
- Add a lock and use it for guarding access to events_array dynamic array.
- Add checks everywhere where new instance of THD is created. NULL can be
returned and this will crash the server. The server will crash probably
later but should not be in this code! Add a global variable, and a lock
to guard it, that will specify an error in a worker thread so preventing
new threads from being spawned.
5. Move executor related code to event_executor.cc and .h
6. Maybe move all allocations during parsing to evex_mem_root thus saving
- Maybe move all allocations during parsing to evex_mem_root thus saving
double parsing in evex_create_event!
7. If the server is killed (stopping) try to kill executing events..
8. What happens if one renames an event in the DB while it is in memory?
- 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?
9. created & modified in the table should be UTC?
10. Add a lock to event_timed to serialize execution of an event - do not
- created & modified in the table should be UTC?
- Add a lock to event_timed to serialize execution of an event - do not
allow parallel executions. Hmm, however how last_executed is marked
then? The call to event_timed::mark_last_executed() must be moved to
event_timed::execute()?
11. Consider using conditional variable when doing shutdown instead of
- Consider using conditional variable when doing shutdown instead of
waiting some time (tries < 5).
12. Fix event_timed::get_show_create_event.
13. Add logging to file.
14. Add function documentation whenever needed.
*/
- Make event_timed::get_show_create_event() work
- Add function documentation whenever needed.
- Add logging to file
Warning:
- For now parallel execution is not possible because the same sp_head cannot be
executed few times!!! There is still no lock attached to particular event.
*/
bool mysql_event_table_exists= 1;
DYNAMIC_ARRAY events_array;
DYNAMIC_ARRAY evex_executing_queue;
MEM_ROOT evex_mem_root;
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 :) */
};
#define EVEX_OPEN_TABLE_FOR_UPDATE() \
open_proc_type_table_for_update(thd, "event", &mysql_event_table_exists)
static bool mysql_event_table_exists= 1;
static DYNAMIC_ARRAY events_array;
static DYNAMIC_ARRAY evex_executing_queue;
static MEM_ROOT evex_mem_root;
static uint workers_count;
static bool evex_is_running= false;
static pthread_mutex_t LOCK_event_arrays,
LOCK_workers_count,
LOCK_evex_running;
extern int yyparse(void *thd);
ulong opt_event_executor;
my_bool event_executor_running_global_var= false;
extern ulong thread_created;
//extern volatile uint thread_running;
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
......@@ -89,7 +92,7 @@ extern ulong thread_created;
a < b -> -1
*/
static inline int
inline int
my_time_compare(TIME *a, TIME *b)
{
/*
......@@ -140,17 +143,10 @@ my_time_compare(TIME *a, TIME *b)
}
static int
inline int
event_timed_compare(event_timed **a, event_timed **b)
{
return my_time_compare(&(*a)->m_execute_at, &(*b)->m_execute_at);
/*
if (a->sort > b->sort)
return -1;
if (a->sort < b->sort)
return 1;
return 0;
*/
}
......@@ -210,15 +206,14 @@ evex_fill_row(THD *thd, TABLE *table, event_timed *et)
if (et->m_expr)
{
// m_expr was fixed in init_interval
// et->m_expr->save_in_field(table->field[EVEX_FIELD_INTERVAL_EXPR], (my_bool)TRUE);
table->field[EVEX_FIELD_INTERVAL_EXPR]->set_notnull();
table->field[EVEX_FIELD_INTERVAL_EXPR]->store((longlong)et->m_expr);
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!
/*
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->m_interval + 1);
}
else if (et->m_execute_at.year)
......@@ -243,10 +238,6 @@ evex_fill_row(THD *thd, TABLE *table, event_timed *et)
store((et->m_comment).str, (et->m_comment).length, system_charset_info);
DBUG_RETURN(0);
parse_error:
DBUG_RETURN(EVEX_PARSE_ERROR);
general_error:
DBUG_RETURN(EVEX_GENERAL_ERROR);
get_field_failed:
DBUG_RETURN(EVEX_GET_FIELD_FAILED);
}
......@@ -299,11 +290,6 @@ db_create_event(THD *thd, event_timed *et)
restore_record(table, s->default_values); // Get default values for fields
strxmov(definer, et->m_definer_user.str, "@", et->m_definer_host.str, NullS);
if (table->s->fields != EVEX_FIELD_COUNT)
{
ret= EVEX_GET_FIELD_FAILED;
goto done;
}
/* TODO : Uncomment these and add handling in sql_parse.cc or here
if (sp->m_name.length > table->field[MYSQL_PROC_FIELD_NAME]->field_length)
......@@ -477,6 +463,7 @@ evex_load_and_compile_event(THD * thd, sp_name *spn, bool use_lock)
allocate on evex_mem_root. call without evex_mem_root and
and m_sphead will not be cleared!
*/
if ((ret= ett->compile(thd, &evex_mem_root)))
{
thd->mem_root= tmp_mem_root;
......@@ -554,27 +541,23 @@ evex_remove_from_cache(LEX_STRING *db, LEX_STRING *name, bool use_lock)
//we are lucky the event is in the executing queue, no need of second pass
need_second_pass= false;
idx= get_index_dynamic(&events_array, (gptr) ett);
if (idx != -1)
{
//destruct first and then remove. the destructor will delete sp_head
ett->free_sp();
delete_dynamic_element(&events_array, idx);
delete_dynamic_element(&evex_executing_queue, i);
}
else
if (idx == -1)
{
//this should never happen
DBUG_PRINT("error", ("Sth weird with get_index_dynamic. %d."
DBUG_PRINT("error", (" get_index_dynamic problem. %d."
"i=%d idx=%d evex_ex_queue.buf=%p evex_ex_queue.elements=%d ett=%p\n"
"events_array=%p events_array.elements=%d events_array.buf=%p\n"
"p_et=%p ett=%p",
__LINE__, i, idx, &evex_executing_queue.buffer,
evex_executing_queue.elements, ett, &events_array,
events_array.elements, events_array.buffer, p_et, ett));
ret= EVEX_GENERAL_ERROR;
goto done;
DBUG_ASSERT(0);
}
//ok we have cleaned
//destruct first and then remove. the destructor will delete sp_head
ett->free_sp();
delete_dynamic_element(&events_array, idx);
delete_dynamic_element(&evex_executing_queue, i);
// ok, we have cleaned
}
}
......@@ -767,41 +750,6 @@ done_no_evex:
}
/*
Checks for existance of a specified event
SYNOPSIS
evex_event_exists()
thd THD
et event's name
NOTES
Currently unused
*/
bool
evex_event_exists(THD *thd, event_timed *et)
{
TABLE *table;
int ret;
bool opened= FALSE;
Open_tables_state open_tables_state_backup;
DBUG_ENTER("evex_event_exists");
if (!(table= open_proc_type_table_for_read(thd, &open_tables_state_backup,
"event", &mysql_event_table_exists)))
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
ret= sp_db_find_routine_aux(thd, 0/*notype*/, et->m_db, et->m_name, table);
close_thread_tables(thd);
thd->restore_backup_open_tables_state(&open_tables_state_backup);
thd->clear_error();
DBUG_RETURN(ret == SP_OK);
}
/*
Drops an event
......@@ -868,1443 +816,4 @@ done:
}
/*
!!! This one is executor related so maybe moving it to
event_executor.cc is a good idea or ?
*/
static int
evex_load_events_from_db(THD *thd)
{
TABLE *table;
READ_RECORD read_record_info;
MYSQL_LOCK *lock;
Open_tables_state open_tables_state_backup;
int ret= -1;
DBUG_ENTER("evex_load_events_from_db");
if (!(table= open_proc_type_table_for_read(thd, &open_tables_state_backup,
"event", &mysql_event_table_exists)))
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, *et_copy;
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 (et->load_from_row(&evex_mem_root, table))
//error loading!
continue;
DBUG_PRINT("evex_load_events_from_db",
("Event %s loaded from row. Time to compile", et->m_name.str));
if (et->compile(thd, &evex_mem_root))
//problem during compile
continue;
// let's find when to be executed
et->compute_next_execution_time();
DBUG_PRINT("evex_load_events_from_db",
("Adding %s to the executor list.", et->m_name.str));
VOID(push_dynamic(&events_array,(gptr) et));
// we always add at the end so the number of elements - 1 is the place
// in the buffer
et_copy= dynamic_element(&events_array, events_array.elements - 1,
event_timed*);
VOID(push_dynamic(&evex_executing_queue,(gptr) &et_copy));
et->m_free_sphead_on_delete= false;
DBUG_PRINT("info", (""));
delete et;
}
end_read_record(&read_record_info);
qsort((gptr) dynamic_element(&evex_executing_queue, 0, event_timed**),
evex_executing_queue.elements,
sizeof(event_timed **),
(qsort_cmp) event_timed_compare
);
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
thd->version--; // Force close to free memory
ret= 0;
end:
close_thread_tables(thd);
thd->restore_backup_open_tables_state(&open_tables_state_backup);
DBUG_PRINT("evex_load_events_from_db",
("Events loaded from DB. Status code %d", ret));
DBUG_RETURN(ret);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//////////////// EVENT_TIMED class /////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/*
Init all member variables
SYNOPSIS
event_timed::init()
*/
void
event_timed::init()
{
DBUG_ENTER("event_timed::init");
m_qname.str= m_db.str= m_name.str= m_body.str= m_comment.str= 0;
m_qname.length= m_db.length= m_name.length= m_body.length= m_comment.length= 0;
set_zero_time(&m_starts, MYSQL_TIMESTAMP_DATETIME);
set_zero_time(&m_ends, MYSQL_TIMESTAMP_DATETIME);
set_zero_time(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
set_zero_time(&m_last_executed, MYSQL_TIMESTAMP_DATETIME);
m_definer_user.str= m_definer_host.str= 0;
m_definer_user.length= m_definer_host.length= 0;
DBUG_VOID_RETURN;
}
/*
Set a name of the event
SYNOPSIS
event_timed::init_name()
thd THD
name the name extracted in the parser
*/
void
event_timed::init_name(THD *thd, sp_name *name)
{
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 (name)
{
m_db.length= name->m_db.length;
if (name->m_db.length == 0)
m_db.str= NULL;
else
m_db.str= strmake_root(root, name->m_db.str, name->m_db.length);
m_name.length= name->m_name.length;
m_name.str= strmake_root(root, name->m_name.str, name->m_name.length);
if (name->m_qname.length == 0)
name->init_qname(thd);
m_qname.length= name->m_qname.length;
m_qname.str= strmake_root(root, name->m_qname.str, m_qname.length);
}
else if (thd->db)
{
m_db.length= thd->db_length;
m_db.str= strmake_root(root, thd->db, m_db.length);
}
DBUG_PRINT("m_db", ("len=%d db=%s",m_db.length, m_db.str));
DBUG_PRINT("m_name", ("len=%d name=%s",m_name.length, m_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;
m_body.length= thd->lex->ptr - m_body_begin;
// Trim nuls at the end
while (m_body.length && m_body_begin[m_body.length-1] == '\0')
m_body.length--;
m_body.str= strmake_root(root, (char *)m_body_begin, m_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(&ltime, 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(&ltime, TIME_to_timestamp(thd,&ltime, &not_used));
m_execute_at= ltime;
DBUG_RETURN(0);
}
/*
Set time for execution for transient events.
SYNOPSIS
event_timed::init_interval()
expr how much?
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 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);
m_expr= tmp;
m_interval= 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 *starts)
{
my_bool not_used;
TIME ltime;
my_time_t my_time_tmp;
DBUG_ENTER("event_timed::init_starts");
if (starts->fix_fields(thd, &starts))
DBUG_RETURN(EVEX_PARSE_ERROR);
if (starts->val_int() == MYSQL_TIMESTAMP_ERROR)
DBUG_RETURN(EVEX_BAD_PARAMS);
if ((not_used= starts->get_date(&ltime, 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(&ltime, TIME_to_timestamp(thd,&ltime, &not_used));
m_starts= ltime;
DBUG_RETURN(0);
}
/*
Set deactivation time.
SYNOPSIS
event_timed::init_ends()
thd THD
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 *ends)
{
TIME ltime;
my_time_t my_time_tmp;
my_bool not_used;
DBUG_ENTER("event_timed::init_ends");
if (ends->fix_fields(thd, &ends))
DBUG_RETURN(EVEX_PARSE_ERROR);
// the field was already fixed in init_ends
if ((not_used= ends->get_date(&ltime, 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(&ltime, TIME_to_timestamp(thd, &ltime, &not_used));
if (m_starts.year && my_time_compare(&m_starts, &ltime) != -1)
DBUG_RETURN(EVEX_BAD_PARAMS);
m_ends= ltime;
DBUG_RETURN(0);
}
/*
Set behaviour when ENDS has been set and passed by.
SYNOPSIS
event_timed::init_interval()
drop if set then drop the event otherwise preserve it.
*/
void
event_timed::set_on_completion_drop(bool drop)
{
DBUG_ENTER("event_timed::set_on_completion");
if (drop)
m_on_completion= MYSQL_EVENT_ON_COMPLETION_DROP;
else
m_on_completion= MYSQL_EVENT_ON_COMPLETION_PRESERVE;
DBUG_VOID_RETURN;
}
/*
Sets event's status. DISABLED - not executable even if
everything else is ok (STARTS, ENDS, INTERVAL and so on).
SYNOPSIS
event_timed::set_event_status()
enabled set whether enabled or not.
*/
void
event_timed::set_event_status(bool enabled)
{
DBUG_ENTER("event_timed::set_on_completion");
m_status_changed= true;
if (enabled)
m_status= MYSQL_EVENT_ENABLED;
else
m_status= MYSQL_EVENT_DISABLED;
DBUG_VOID_RETURN;
}
/*
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 *comment)
{
DBUG_ENTER("event_timed::init_comment");
MEM_ROOT *root= thd->mem_root;
m_comment.length= comment->length;
m_comment.str= strmake_root(root, comment->str, comment->length);
DBUG_PRINT("m_comment", ("len=%d",m_comment.length));
DBUG_VOID_RETURN;
}
/*
Inits definer (m_definer_user and m_definer_host) during
parsing.
SYNOPSIS
event_timed::init_definer()
*/
int
event_timed::init_definer(THD *thd)
{
DBUG_ENTER("event_timed::init_definer");
m_definer_user.str= strdup_root(thd->mem_root, thd->security_ctx->priv_user);
m_definer_user.length= strlen(thd->security_ctx->priv_user);
m_definer_host.str= strdup_root(thd->mem_root, thd->security_ctx->priv_host);
m_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()
*/
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->m_db.str= get_field(mem_root,
table->field[EVEX_FIELD_DB])) == NULL)
goto error;
et->m_db.length= strlen(et->m_db.str);
if ((et->m_name.str= get_field(mem_root,
table->field[EVEX_FIELD_NAME])) == NULL)
goto error;
et->m_name.length= strlen(et->m_name.str);
if ((et->m_body.str= get_field(mem_root,
table->field[EVEX_FIELD_BODY])) == NULL)
goto error;
et->m_body.length= strlen(et->m_body.str);
if ((et->m_definer.str= get_field(mem_root,
table->field[EVEX_FIELD_DEFINER])) == NullS)
goto error;
et->m_definer.length= strlen(et->m_definer.str);
ptr= strchr(et->m_definer.str, '@');
if (! ptr)
ptr= et->m_definer.str; // Weird, isn't it?
len= ptr - et->m_definer.str;
et->m_definer_user.str= strmake_root(mem_root, et->m_definer.str, len);
et->m_definer_user.length= len;
len= et->m_definer.length - len - 1; //1 is because of @
et->m_definer_host.str= strmake_root(mem_root, ptr + 1, len);//1: because of @
et->m_definer_host.length= len;
res1= table->field[EVEX_FIELD_STARTS]->
get_date(&et->m_starts, TIME_NO_ZERO_DATE);
res2= table->field[EVEX_FIELD_ENDS]->
get_date(&et->m_ends, TIME_NO_ZERO_DATE);
et->m_expr= 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->m_expr && table->field[EVEX_FIELD_EXECUTE_AT]->
get_date(&et->m_execute_at, TIME_NO_ZERO_DATE))
goto error;
/*
In DB the values start from 1 but enum interval_type starts
from 0
*/
et->m_interval= (interval_type)
((ulonglong) table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->val_int() - 1);
et->m_modified= table->field[EVEX_FIELD_CREATED]->val_int();
et->m_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() m_last_executed gets time_now
and not m_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(&m_last_executed, MYSQL_TIMESTAMP_DATETIME);
#ifdef ANDREY_0
table->field[EVEX_FIELD_LAST_EXECUTED]->
get_date(&et->m_last_executed, TIME_NO_ZERO_DATE);
#endif
m_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->m_name.str, ptr));
et->m_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->m_on_completion= (ptr[0]=='D'? MYSQL_EVENT_ON_COMPLETION_DROP:
MYSQL_EVENT_ON_COMPLETION_PRESERVE);
et->m_comment.str= get_field(mem_root, table->field[EVEX_FIELD_COMMENT]);
if (et->m_comment.str != NullS)
et->m_comment.length= strlen(et->m_comment.str);
else
et->m_comment.length= 0;
DBUG_RETURN(0);
error:
DBUG_RETURN(EVEX_GET_FIELD_FAILED);
}
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 (m_status == MYSQL_EVENT_DISABLED)
{
DBUG_PRINT("compute_next_execution_time",
("Event %s is DISABLED", m_name.str));
goto ret;
}
//if one-time no need to do computation
if (!m_expr)
{
//let's check whether it was executed
if (m_last_executed.year)
{
DBUG_PRINT("compute_next_execution_time",
("One-time event %s was already executed", m_name.str));
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
{
DBUG_PRINT("compute_next_execution_time",
("One-time event will be dropped."));
m_dropped= true;
}
m_status= MYSQL_EVENT_DISABLED;
m_status_changed= true;
}
goto ret;
}
time(&now);
my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
/*
sql_print_information("[%s.%s]", m_db.str, m_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("m_starts : [%d-%d-%d %d:%d:%d ]", m_starts.year, m_starts.month, m_starts.day, m_starts.hour, m_starts.minute, m_starts.second);
sql_print_information("m_ends : [%d-%d-%d %d:%d:%d ]", m_ends.year, m_ends.month, m_ends.day, m_ends.hour, m_ends.minute, m_ends.second);
sql_print_information("m_last_ex: [%d-%d-%d %d:%d:%d ]", m_last_executed.year, m_last_executed.month, m_last_executed.day, m_last_executed.hour, m_last_executed.minute, m_last_executed.second);
*/
//if time_now is after m_ends don't execute anymore
if (m_ends.year && (tmp= my_time_compare(&m_ends, &time_now)) == -1)
{
// time_now is after m_ends. don't execute anymore
set_zero_time(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
m_dropped= true;
m_status= MYSQL_EVENT_DISABLED;
m_status_changed= true;
goto ret;
}
/*
Here time_now is before or equals m_ends if the latter is set.
Let's check whether time_now is before m_starts.
If so schedule for m_starts
*/
if (m_starts.year && (tmp= my_time_compare(&time_now, &m_starts)) < 1)
{
if (tmp == 0 && my_time_compare(&m_starts, &m_last_executed) == 0)
{
/*
time_now = m_starts = m_last_executed
do nothing or we will schedule for second time execution at m_starts.
*/
}
else
{
//m_starts is in the future
//time_now before m_starts. Scheduling for m_starts
m_execute_at= m_starts;
goto ret;
}
}
if (m_starts.year && m_ends.year)
{
/*
Both m_starts and m_ends are set and time_now is between them (incl.)
If m_last_executed is set then increase with m_expr. The new TIME is
after m_ends set m_execute_at to 0. And check for m_on_completion
If not set then schedule for now.
*/
if (!m_last_executed.year)
m_execute_at= time_now;
else
{
my_time_t last, ll_ends;
// There was previous execution
last= sec_since_epoch_TIME(&m_last_executed) + m_expr;
ll_ends= sec_since_epoch_TIME(&m_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(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
m_dropped= true;
}
else
my_tz_UTC->gmt_sec_to_TIME(&m_execute_at, last);
}
goto ret;
}
else if (!m_starts.year && !m_ends.year)
{
// both m_starts and m_ends are not set, se we schedule for the next
// based on m_last_executed
if (!m_last_executed.year)
//m_last_executed not set. Schedule the event for now
m_execute_at= time_now;
else
//ToDo Andrey: maybe check for error here?
my_tz_UTC->gmt_sec_to_TIME(&m_execute_at,
sec_since_epoch_TIME(&m_last_executed) + m_expr);
goto ret;
}
else
{
//either m_starts or m_ends is set
if (m_starts.year)
{
/*
- m_starts is set.
- m_starts is not in the future according to check made before
Hence schedule for m_starts + m_expr in case m_last_executed
is not set, otherwise to m_last_executed + m_expr
*/
my_time_t last;
//convert either m_last_executed or m_starts to seconds
if (m_last_executed.year)
last= sec_since_epoch_TIME(&m_last_executed) + m_expr;
else
last= sec_since_epoch_TIME(&m_starts);
//now convert back to TIME
//ToDo Andrey: maybe check for error here?
my_tz_UTC->gmt_sec_to_TIME(&m_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_expr.
If m_last_executed is not set then schedule for now
*/
my_time_t last, ll_ends;
if (!m_last_executed.year)
m_execute_at= time_now;
else
{
last= sec_since_epoch_TIME(&m_last_executed);
ll_ends= sec_since_epoch_TIME(&m_ends);
last+= m_expr;
//now convert back to TIME
//ToDo Andrey: maybe check for error here?
if (ll_ends < last)
{
set_zero_time(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
m_dropped= true;
}
else
my_tz_UTC->gmt_sec_to_TIME(&m_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);
m_last_executed= time_now; // was m_execute_at
#ifdef ANDREY_0
m_last_executed= m_execute_at;
#endif
m_last_executed_changed= true;
}
bool
event_timed::drop(THD *thd)
{
DBUG_ENTER("event_timed::drop");
if (evex_drop_event(thd, this, false))
DBUG_RETURN(true);
DBUG_RETURN(false);
}
bool
event_timed::update_fields(THD *thd)
{
TABLE *table;
int ret= 0;
bool opened;
DBUG_ENTER("event_timed::update_time_fields");
DBUG_PRINT("enter", ("name: %*s", m_name.length, m_name.str));
//no need to update if nothing has changed
if (!(m_status_changed || m_last_executed_changed))
goto done;
if (!(table= EVEX_OPEN_TABLE_FOR_UPDATE()))
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
if ((ret= sp_db_find_routine_aux(thd, 0/*notype*/, m_db, m_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 (m_last_executed_changed)
{
table->field[EVEX_FIELD_LAST_EXECUTED]->set_notnull();
table->field[EVEX_FIELD_LAST_EXECUTED]->store_time(&m_last_executed,
MYSQL_TIMESTAMP_DATETIME);
m_last_executed_changed= false;
}
if (m_status_changed)
{
table->field[EVEX_FIELD_STATUS]->set_notnull();
table->field[EVEX_FIELD_STATUS]->store((longlong)m_status);
m_status_changed= false;
}
if ((table->file->update_row(table->record[1],table->record[0])))
ret= EVEX_WRITE_ROW_FAILED;
done:
close_thread_tables(thd);
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 ") + m_db.length + strlen(".") + m_name.length +
strlen(" ON SCHEDULE ") + strlen("EVERY 5 MINUTE ")
/*
+ strlen("ON COMPLETION ")
+ (m_on_completion==MYSQL_EVENT_ON_COMPLETION_DROP?
strlen("NOT PRESERVE "):strlen("PRESERVE "))
+ (m_status==MYSQL_EVENT_ENABLED?
strlen("ENABLE "):strlen("DISABLE "))
+ strlen("COMMENT \"") + m_comment.length + strlen("\" ")
*/
+ strlen("DO ") +
+ m_body.length + strlen(";");
ret= dst= (char*) alloc_root(thd->mem_root, len);
memcpy(dst, "CREATE EVENT ", tmp_len= strlen("CREATE EVENT "));
dst+= tmp_len;
memcpy(dst, m_db.str, tmp_len=m_db.length);
dst+= tmp_len;
memcpy(dst, ".", tmp_len= strlen("."));
dst+= tmp_len;
memcpy(dst, m_name.str, tmp_len= m_name.length);
dst+= tmp_len;
memcpy(dst, " ON SCHEDULE ", tmp_len= strlen(" ON SCHEDULE "));
dst+= tmp_len;
memcpy(dst, "EVERY 5 MINUTE ", tmp_len= strlen("EVERY 5 MINUTE "));
dst+= tmp_len;
/*
memcpy(dst, "ON COMPLETION ", tmp_len =strlen("ON COMPLETION "));
dst+= tmp_len;
memcpy(dst, (m_on_completion==MYSQL_EVENT_ON_COMPLETION_DROP?
"NOT PRESERVE ":"PRESERVE "),
tmp_len =(m_on_completion==MYSQL_EVENT_ON_COMPLETION_DROP? 13:9));
dst+= tmp_len;
memcpy(dst, (m_status==MYSQL_EVENT_ENABLED?
"ENABLE ":"DISABLE "),
tmp_len= (m_status==MYSQL_EVENT_ENABLED? 8:9));
dst+=tmp_len;
memcpy(dst, "COMMENT \"", tmp_len= strlen("COMMENT \""));
dst+= tmp_len;
memcpy(dst, m_comment.str, tmp_len= m_comment.length);
dst+= tmp_len;
memcpy(dst, "\" ", tmp_len=2);
dst+= tmp_len;
*/
memcpy(dst, "DO ", tmp_len=3);
dst+= tmp_len;
memcpy(dst, m_body.str, tmp_len= m_body.length);
dst+= tmp_len;
memcpy(dst, ";", 1);
++dst;
*dst= '\0';
*length= len;
return ret;
}
int
event_timed::execute(THD *thd, MEM_ROOT *mem_root= NULL)
{
List<Item> empty_item_list;
int ret= 0;
DBUG_ENTER("event_timed::execute");
// TODO Andrey : make this as member variable and delete in destructor
empty_item_list.empty();
if (!m_sphead && (ret= compile(thd, mem_root)))
goto done;
ret= m_sphead->execute_procedure(thd, &empty_item_list);
done:
// Don't cache m_sphead if allocated on another mem_root
if (mem_root && m_sphead)
{
delete m_sphead;
m_sphead= 0;
}
DBUG_RETURN(ret);
}
int
event_timed::compile(THD *thd, MEM_ROOT *mem_root= NULL)
{
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;
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= m_db.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;
DBUG_RETURN(-1);
}
m_sphead= lex.sphead;
m_sphead->m_db= m_db;
//copy also chistics since they will vanish otherwise we get 0x0 pointer
// Todo : Handle sql_mode !!
m_sphead->set_definer(m_definer.str, m_definer.length);
m_sphead->set_info(0, 0, &lex.sp_chistics, 0/*sql_mode*/);
m_sphead->optimize();
lex_end(&lex);
thd->lex= old_lex;
thd->query= old_query;
thd->query_length= old_query_len;
thd->db= old_db;
/*
Change the memory root for the execution time.
*/
if (mem_root)
thd->mem_root= tmp_mem_root;
DBUG_RETURN(0);
}
/******************************** EXECUTOR ************************************/
//extern "C" pthread_handler_decl(event_executor_main, arg);
//extern "C" pthread_handler_decl(event_executor_worker, arg);
/*
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);
int
init_events()
{
pthread_t th;
DBUG_ENTER("init_events");
DBUG_PRINT("info",("Starting events main thread"));
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);
VOID(pthread_mutex_lock(&LOCK_evex_running));
evex_is_running= false;
event_executor_running_global_var= false;
VOID(pthread_mutex_unlock(&LOCK_evex_running));
//TODO Andrey: Change the error code returned!
if (pthread_create(&th, NULL, event_executor_main, (void*)NULL))
DBUG_RETURN(ER_SLAVE_THREAD);
DBUG_RETURN(0);
}
void
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);
}
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;
DBUG_ENTER("event_executor_main");
DBUG_PRINT("event_executor_main", ("EVEX thread started"));
VOID(pthread_mutex_lock(&LOCK_evex_running));
evex_is_running= true;
event_executor_running_global_var= opt_event_executor;
VOID(pthread_mutex_unlock(&LOCK_evex_running));
// 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;
thd->init_for_queries();
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_array"));
VOID(pthread_mutex_lock(&LOCK_event_arrays));
/*
my_malloc is used as underlying allocator which does not use a mem_root
thus data should be freed at later stage.
*/
VOID(my_init_dynamic_array(&events_array, sizeof(event_timed), 50, 100));
VOID(my_init_dynamic_array(&evex_executing_queue, sizeof(event_timed *), 50, 100));
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
if (evex_load_events_from_db(thd))
goto err;
THD_CHECK_SENTRY(thd);
/* Read queries from the IO/THREAD until this thread is killed */
while (!thd->killed)
{
TIME time_now;
my_time_t now;
my_ulonglong cnt;
DBUG_PRINT("info", ("EVEX External Loop %d", ++cnt));
// sql_print_information("[EVEX] External Loop!");
my_sleep(500000);// sleep 0.5s
if (!event_executor_running_global_var)
continue;
time(&now);
my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
VOID(pthread_mutex_lock(&LOCK_event_arrays));
for (i= 0; (i < evex_executing_queue.elements) && !thd->killed; ++i)
{
event_timed **p_et=dynamic_element(&evex_executing_queue,i,event_timed**);
event_timed *et= *p_et;
// sql_print_information("[EVEX] External Loop 2!");
if (!event_executor_running_global_var)
break;// soon we will do only continue (see the code a bit above)
thd->proc_info = "Iterating";
THD_CHECK_SENTRY(thd);
/*
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->m_execute_at.year &&
my_time_compare(&time_now, &et->m_execute_at) == -1)
break;
if (et->m_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);
if (pthread_create(&th, NULL, event_executor_worker, (void*)et))
{
sql_print_error("Problem while trying to create a thread");
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
goto err; // for now finish execution of the Executor
}
et->mark_last_executed();
et->compute_next_execution_time();
et->update_fields(thd);
if ((et->m_execute_at.year && !et->m_expr)
|| TIME_to_ulonglong_datetime(&et->m_execute_at) == 0L)
et->m_flags |= EVENT_EXEC_NO_MORE;
}
}
/*
Let's remove elements which won't be executed any more
The number is "i" and it is <= up to evex_executing_queue.elements
*/
j= 0;
while (j < i && j < evex_executing_queue.elements)
{
event_timed **p_et= dynamic_element(&evex_executing_queue, j, event_timed**);
event_timed *et= *p_et;
if (et->m_flags & EVENT_EXEC_NO_MORE || et->m_status == MYSQL_EVENT_DISABLED)
{
delete_dynamic_element(&evex_executing_queue, j);
DBUG_PRINT("", ("DELETING FROM EXECUTION QUEUE [%s.%s]",et->m_db.str, et->m_name.str));
// nulling the position, will delete later
if (et->m_dropped)
{
// we have to drop the event
int idx;
et->drop(thd);
idx= get_index_dynamic(&events_array, (gptr) et);
if (idx != -1)
delete_dynamic_element(&events_array, idx);
else
sql_print_error("Something weird happened with events. %d", __LINE__);
}
continue;
}
++j;
}
if (evex_executing_queue.elements)
//ToDo Andrey : put a lock here
qsort((gptr) dynamic_element(&evex_executing_queue, 0, event_timed**),
evex_executing_queue.elements,
sizeof(event_timed **),
(qsort_cmp) event_timed_compare
);
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
}// while (!thd->killed)
err:
VOID(pthread_mutex_lock(&LOCK_evex_running));
evex_is_running= false;
VOID(pthread_mutex_unlock(&LOCK_evex_running));
sql_print_information("Event executor stopping");
// LEX_STRINGs reside in the memory root and will be destroyed with it.
// Hence no need of delete but only freeing of SP
for (i=0; i < events_array.elements; ++i)
{
event_timed *et= dynamic_element(&events_array, i, event_timed*);
et->free_sp();
}
// TODO Andrey: USE lock here!
delete_dynamic(&evex_executing_queue);
delete_dynamic(&events_array);
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--;
THD_CHECK_SENTRY(thd);
delete thd;
pthread_mutex_unlock(&LOCK_thread_count);
/*
sleeping some time may help not crash the server. sleeping
is done to wait for spawned threads to finish.
TODO: A better will be with a conditional variable
*/
{
uint tries= 0;
while (tries++ < 5)
{
VOID(pthread_mutex_lock(&LOCK_workers_count));
if (!workers_count)
{
VOID(pthread_mutex_unlock(&LOCK_workers_count));
break;
}
VOID(pthread_mutex_unlock(&LOCK_workers_count));
DBUG_PRINT("info", ("Sleep %d", tries));
my_sleep(1000000 * tries);// 1s
}
DBUG_PRINT("info", ("Maybe now it is ok to kill the thread and evex MRoot"));
}
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 executor stopped");
shutdown_events();
my_thread_end();
pthread_exit(0);
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 */
List<Item> empty_item_list;
event_timed *event = (event_timed *) event_void;
MEM_ROOT mem_root;
ulong save_options;
DBUG_ENTER("event_executor_worker");
VOID(pthread_mutex_lock(&LOCK_workers_count));
++workers_count;
VOID(pthread_mutex_unlock(&LOCK_workers_count));
init_alloc_root(&mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
//we pass this empty list as parameter to the SP_HEAD of the event
empty_item_list.empty();
my_thread_init();
//TODO Andrey: Check for NULL
if (!(thd = new THD)) // note that contructor of THD uses DBUG_ !
{
sql_print_error("Cannot create a THD structure in worker thread");
goto err_no_thd;
}
thd->thread_stack = (char*)&thd; // remember where our stack is
thd->mem_root= &mem_root;
// pthread_detach_this_thread();
pthread_detach(pthread_self());
if (init_event_thread(thd))
goto err;
thd->init_for_queries();
save_options= thd->options;
thd->options&= ~OPTION_BIN_LOG;
VOID(pthread_mutex_lock(&LOCK_thread_count));
threads.append(thd);
thread_count++;
thread_running++;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
//thd->security_ctx->priv_host is char[MAX_HOSTNAME]
strxnmov(thd->security_ctx->priv_host, sizeof(thd->security_ctx->priv_host),
event->m_definer_host.str, NullS);
thd->security_ctx->priv_user= event->m_definer_user.str;
thd->db= event->m_db.str;
if (!check_global_access(thd, EVENT_ACL))
{
char exec_time[200];
int ret;
my_TIME_to_str(&event->m_execute_at, exec_time);
DBUG_PRINT("info", (" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time));
// sql_print_information(" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time);
ret= event->execute(thd);
// sql_print_information(" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]. RetCode=%d", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time, ret);
DBUG_PRINT("info", (" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time));
}
thd->db= 0;
//reenable (is it needed?)
thd->options= save_options;
err:
VOID(pthread_mutex_lock(&LOCK_thread_count));
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)).
*/
//thd->query= thd->db= thd->catalog= 0;
//thd->query_length= thd->db_length= 0;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
//thd->temporary_tables = 0; // remove tempation from destructor to close them
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;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
err_no_thd:
free_root(&mem_root, MYF(0));
// sql_print_information(" Worker thread exiting");
VOID(pthread_mutex_lock(&LOCK_workers_count));
--workers_count;
VOID(pthread_mutex_unlock(&LOCK_workers_count));
my_thread_end();
pthread_exit(0);
DBUG_RETURN(0); // Can't return anything here
}
/* -*- C++ -*- */
/* Copyright (C) 2000-2003 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_head.h"
#include "sp.h"
extern ulong opt_event_executor;
#define EVEX_OK 0
#define EVEX_KEY_NOT_FOUND -1
#define EVEX_OPEN_TABLE_FAILED -2
#define EVEX_WRITE_ROW_FAILED -3
#define EVEX_DELETE_ROW_FAILED -4
#define EVEX_GET_FIELD_FAILED -5
#define EVEX_PARSE_ERROR -6
#define EVEX_INTERNAL_ERROR -7
#define EVEX_NO_DB_ERROR -8
#define EVEX_GENERAL_ERROR -9
#define EVEX_BAD_PARAMS -10
#define EVEX_NOT_RUNNING -11
#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_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)
#define SP_OK 0
#define SP_KEY_NOT_FOUND -1
#define SP_OPEN_TABLE_FAILED -2
#define SP_WRITE_ROW_FAILED -3
#define SP_DELETE_ROW_FAILED -4
#define SP_GET_FIELD_FAILED -5
#define SP_PARSE_ERROR -6
#define SP_INTERNAL_ERROR -7
#define SP_NO_DB_ERROR -8
#define SP_BAD_IDENTIFIER -9
#define SP_BODY_TOO_LONG -10
extern ulong opt_event_executor;
enum enum_event_on_completion
{
......@@ -40,11 +71,12 @@ class event_timed
{
event_timed(const event_timed &); /* Prevent use of these */
void operator=(event_timed &);
my_bool running;
pthread_mutex_t LOCK_running;
public:
LEX_STRING m_db;
LEX_STRING m_name;
LEX_STRING m_qname; // db.name
LEX_STRING m_body;
LEX_STRING m_definer_user;
......@@ -64,9 +96,6 @@ public:
enum enum_event_status m_status;
sp_head *m_sphead;
uint m_old_cmq; // Old CLIENT_MULTI_QUERIES value
const uchar *m_body_begin;
bool m_dropped;
......@@ -75,15 +104,19 @@ public:
bool m_last_executed_changed;
bool m_status_changed;
event_timed():m_expr(0), m_created(0), m_modified(0),
event_timed():running(0), m_expr(0), m_created(0), m_modified(0),
m_on_completion(MYSQL_EVENT_ON_COMPLETION_DROP),
m_status(MYSQL_EVENT_ENABLED), m_sphead(0), m_dropped(false),
m_free_sphead_on_delete(true), m_flags(0),
m_last_executed_changed(false), m_status_changed(false)
{ init(); }
{
pthread_mutex_init(&LOCK_running, MY_MUTEX_INIT_FAST);
init();
}
~event_timed()
{
pthread_mutex_destroy(&LOCK_running);
if (m_free_sphead_on_delete)
free_sp();
}
......@@ -115,12 +148,6 @@ public:
void
init_comment(THD *thd, LEX_STRING *comment);
void
set_on_completion_drop(bool drop);
void
set_event_status(bool enabled);
int
load_from_row(MEM_ROOT *mem_root, TABLE *table);
......@@ -140,10 +167,10 @@ public:
get_show_create_event(THD *thd, uint *length);
int
execute(THD *thd, MEM_ROOT *mem_root);
execute(THD *thd, MEM_ROOT *mem_root= NULL);
int
compile(THD *thd, MEM_ROOT *mem_root);
compile(THD *thd, MEM_ROOT *mem_root= NULL);
void free_sp()
{
......@@ -172,29 +199,21 @@ init_events();
void
shutdown_events();
/*
typedef struct st_event_item {
my_time_t execute_at;
sp_head *proc;
char *definer_user;
char *definer_host;
} EVENT_ITEM;
*/
// auxiliary
int
event_timed_compare(event_timed **a, event_timed **b);
/*
CREATE TABLE `event` (
`db` varchar(64) character set latin1 collate latin1_bin NOT NULL default '',
`name` varchar(64) NOT NULL default '',
`body` blob NOT NULL,
`definer` varchar(77) character set latin1 collate latin1_bin NOT NULL default '',
`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,
`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 default '0000-00-00 00:00:00',
`modified` timestamp NOT NULL default '0000-00-00 00:00:00',
`last_executed` datetime default NULL,
......@@ -202,9 +221,9 @@ CREATE TABLE `event` (
`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 latin1 collate latin1_bin NOT NULL default '',
`comment` varchar(64) character set utf8 collate utf8_bin NOT NULL default '',
PRIMARY KEY (`db`,`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
) ENGINE=MyISAM DEFAULT CHARSET=utf8
*/
#endif /* _EVENT_H_ */
/* Copyright (C) 2000-2003 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include "mysql_priv.h"
#include "event.h"
#include "event_priv.h"
#include "sp.h"
#define DBUG_FAULTY_THR2
static uint workers_count;
pthread_mutex_t LOCK_event_arrays,
LOCK_workers_count,
LOCK_evex_running;
bool evex_is_running= false;
ulong opt_event_executor;
my_bool event_executor_running_global_var= false;
extern ulong thread_created;
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);
int
init_events()
{
pthread_t th;
DBUG_ENTER("init_events");
DBUG_PRINT("info",("Starting events main thread"));
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);
VOID(pthread_mutex_lock(&LOCK_evex_running));
evex_is_running= false;
event_executor_running_global_var= 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;
DBUG_ENTER("event_executor_main");
DBUG_PRINT("event_executor_main", ("EVEX thread started"));
VOID(pthread_mutex_lock(&LOCK_evex_running));
evex_is_running= true;
event_executor_running_global_var= opt_event_executor;
VOID(pthread_mutex_unlock(&LOCK_evex_running));
// 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= 0;
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_array"));
VOID(pthread_mutex_lock(&LOCK_event_arrays));
/*
my_malloc is used as underlying allocator which does not use a mem_root
thus data should be freed at later stage.
*/
VOID(my_init_dynamic_array(&events_array, sizeof(event_timed), 50, 100));
VOID(my_init_dynamic_array(&evex_executing_queue, sizeof(event_timed *), 50, 100));
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
if (evex_load_events_from_db(thd))
goto err;
THD_CHECK_SENTRY(thd);
/* Read queries from the IO/THREAD until this thread is killed */
while (!thd->killed)
{
TIME time_now;
my_time_t now;
my_ulonglong cnt;
DBUG_PRINT("info", ("EVEX External Loop %d", ++cnt));
// sql_print_information("[EVEX] External Loop!");
thd->proc_info = "Sleeping";
my_sleep(1000000);// sleep 1s
if (!event_executor_running_global_var)
continue;
time(&now);
my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
VOID(pthread_mutex_lock(&LOCK_event_arrays));
for (i= 0; (i < evex_executing_queue.elements) && !thd->killed; ++i)
{
event_timed **p_et=dynamic_element(&evex_executing_queue,i,event_timed**);
event_timed *et= *p_et;
// sql_print_information("[EVEX] External Loop 2!");
if (!event_executor_running_global_var)
break;// soon we will do only continue (see the code a bit above)
thd->proc_info = "Iterating";
THD_CHECK_SENTRY(thd);
/*
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->m_execute_at.year &&
my_time_compare(&time_now, &et->m_execute_at) == -1)
break;
if (et->m_status == MYSQL_EVENT_ENABLED &&
!check_access(thd, EVENT_ACL, et->m_db.str, 0, 0, 0,
is_schema_db(et->m_db.str)))
{
pthread_t th;
DBUG_PRINT("info", (" Spawning a thread %d", ++iter_num));
thd->proc_info = "Starting new thread";
sql_print_information(" Spawning a thread %d", ++iter_num);
#ifndef DBUG_FAULTY_THR
if (pthread_create(&th, NULL, event_executor_worker, (void*)et))
{
sql_print_error("Problem while trying to create a thread");
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
goto err; // for now finish execution of the Executor
}
#else
event_executor_worker((void *) et);
#endif
et->mark_last_executed();
thd->proc_info = "Computing next time";
et->compute_next_execution_time();
et->update_fields(thd);
if ((et->m_execute_at.year && !et->m_expr)
|| TIME_to_ulonglong_datetime(&et->m_execute_at) == 0L)
et->m_flags |= EVENT_EXEC_NO_MORE;
}
}
/*
Let's remove elements which won't be executed any more
The number is "i" and it is <= up to evex_executing_queue.elements
*/
j= 0;
while (j < i && j < evex_executing_queue.elements)
{
event_timed **p_et= dynamic_element(&evex_executing_queue, j, event_timed**);
event_timed *et= *p_et;
if (et->m_flags & EVENT_EXEC_NO_MORE || et->m_status == MYSQL_EVENT_DISABLED)
{
delete_dynamic_element(&evex_executing_queue, j);
DBUG_PRINT("", ("DELETING FROM EXECUTION QUEUE [%s.%s]",et->m_db.str, et->m_name.str));
// nulling the position, will delete later
if (et->m_dropped)
{
// we have to drop the event
int idx;
et->drop(thd);
idx= get_index_dynamic(&events_array, (gptr) et);
DBUG_ASSERT(idx != -1);
delete_dynamic_element(&events_array, idx);
}
continue;
}
++j;
}
if (evex_executing_queue.elements)
//ToDo Andrey : put a lock here
qsort((gptr) dynamic_element(&evex_executing_queue, 0, event_timed**),
evex_executing_queue.elements,
sizeof(event_timed **),
(qsort_cmp) event_timed_compare
);
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
}// while (!thd->killed)
err:
VOID(pthread_mutex_lock(&LOCK_evex_running));
evex_is_running= false;
VOID(pthread_mutex_unlock(&LOCK_evex_running));
sql_print_information("Event executor stopping");
// LEX_STRINGs reside in the memory root and will be destroyed with it.
// Hence no need of delete but only freeing of SP
for (i=0; i < events_array.elements; ++i)
{
event_timed *et= dynamic_element(&events_array, i, event_timed*);
et->free_sp();
}
// TODO Andrey: USE lock here!
delete_dynamic(&evex_executing_queue);
delete_dynamic(&events_array);
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--;
THD_CHECK_SENTRY(thd);
delete thd;
pthread_mutex_unlock(&LOCK_thread_count);
/*
sleeping some time may help not crash the server. sleeping
is done to wait for spawned threads to finish.
TODO: A better will be with a conditional variable
*/
{
uint tries= 0;
while (tries++ < 5)
{
VOID(pthread_mutex_lock(&LOCK_workers_count));
if (!workers_count)
{
VOID(pthread_mutex_unlock(&LOCK_workers_count));
break;
}
VOID(pthread_mutex_unlock(&LOCK_workers_count));
DBUG_PRINT("info", ("Sleep %d", tries));
my_sleep(1000000 * tries);// 1s
}
DBUG_PRINT("info", ("Maybe now it is ok to kill the thread and evex MRoot"));
}
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 executor stopped");
shutdown_events();
my_thread_end();
pthread_exit(0);
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 */
List<Item> empty_item_list;
event_timed *event = (event_timed *) event_void;
MEM_ROOT 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(&mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
//we pass this empty list as parameter to the SP_HEAD of the event
empty_item_list.empty();
my_thread_init();
if (!(thd = new THD)) // note that contructor of THD uses DBUG_ !
{
sql_print_error("Cannot create a THD structure in worker thread");
goto err_no_thd;
}
thd->thread_stack = (char*)&thd; // remember where our stack is
thd->mem_root= &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= 0;
VOID(pthread_mutex_lock(&LOCK_thread_count));
threads.append(thd);
thread_count++;
thread_running++;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
// thd->security_ctx->priv_host is char[MAX_HOSTNAME]
strxnmov(thd->security_ctx->priv_host, sizeof(thd->security_ctx->priv_host),
event->m_definer_host.str, NullS);
thd->security_ctx->priv_user= event->m_definer_user.str;
thd->db= event->m_db.str;
{
char exec_time[200];
int ret;
my_TIME_to_str(&event->m_execute_at, exec_time);
DBUG_PRINT("info", (" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time));
sql_print_information(" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time);
ret= event->execute(thd, &mem_root);
sql_print_information(" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]. RetCode=%d", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time, ret);
DBUG_PRINT("info", (" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->m_db.str, event->m_name.str,(int) event->m_expr, exec_time));
}
thd->db= 0;
err:
VOID(pthread_mutex_lock(&LOCK_thread_count));
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;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
err_no_thd:
free_root(&mem_root, MYF(0));
// sql_print_information(" Worker thread exiting");
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;
Open_tables_state open_tables_state_backup;
int ret= -1;
DBUG_ENTER("evex_load_events_from_db");
if (!(table= open_proc_type_table_for_read(thd, &open_tables_state_backup,
"event", &mysql_event_table_exists)))
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, *et_copy;
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 (et->load_from_row(&evex_mem_root, table))
//error loading!
continue;
DBUG_PRINT("evex_load_events_from_db",
("Event %s loaded from row. Time to compile", et->m_name.str));
if (et->compile(thd, &evex_mem_root))
//problem during compile
continue;
// let's find when to be executed
et->compute_next_execution_time();
DBUG_PRINT("evex_load_events_from_db",
("Adding %s to the executor list.", et->m_name.str));
VOID(push_dynamic(&events_array,(gptr) et));
// we always add at the end so the number of elements - 1 is the place
// in the buffer
et_copy= dynamic_element(&events_array, events_array.elements - 1,
event_timed*);
VOID(push_dynamic(&evex_executing_queue,(gptr) &et_copy));
et->m_free_sphead_on_delete= false;
DBUG_PRINT("info", (""));
delete et;
}
end_read_record(&read_record_info);
qsort((gptr) dynamic_element(&evex_executing_queue, 0, event_timed**),
evex_executing_queue.elements,
sizeof(event_timed **),
(qsort_cmp) event_timed_compare
);
VOID(pthread_mutex_unlock(&LOCK_event_arrays));
thd->version--; // Force close to free memory
ret= 0;
end:
close_thread_tables(thd);
thd->restore_backup_open_tables_state(&open_tables_state_backup);
DBUG_PRINT("evex_load_events_from_db",
("Events loaded from DB. Status code %d", ret));
DBUG_RETURN(ret);
}
/* Copyright (C) 2000-2003 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_
#define EVEX_OPEN_TABLE_FOR_UPDATE() \
open_proc_type_table_for_update(thd, "event", &mysql_event_table_exists)
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 :) */
};
extern bool evex_is_running;
extern bool mysql_event_table_exists;
extern DYNAMIC_ARRAY events_array;
extern DYNAMIC_ARRAY evex_executing_queue;
extern MEM_ROOT evex_mem_root;
extern pthread_mutex_t LOCK_event_arrays,
LOCK_workers_count,
LOCK_evex_running;
int
my_time_compare(TIME *a, TIME *b);
#endif /* _EVENT_PRIV_H_ */
/* Copyright (C) 2000-2003 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include "mysql_priv.h"
#include "event.h"
#include "event_priv.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");
m_db.str= m_name.str= m_body.str= m_comment.str= 0;
m_db.length= m_name.length= m_body.length= m_comment.length= 0;
set_zero_time(&m_starts, MYSQL_TIMESTAMP_DATETIME);
set_zero_time(&m_ends, MYSQL_TIMESTAMP_DATETIME);
set_zero_time(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
set_zero_time(&m_last_executed, MYSQL_TIMESTAMP_DATETIME);
m_definer_user.str= m_definer_host.str= 0;
m_definer_user.length= m_definer_host.length= 0;
DBUG_VOID_RETURN;
}
/*
Set a name of the event
SYNOPSIS
event_timed::init_name()
thd THD
name the name extracted in the parser
*/
void
event_timed::init_name(THD *thd, sp_name *name)
{
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 (name)
{
m_db.length= name->m_db.length;
if (name->m_db.length == 0)
m_db.str= NULL;
else
m_db.str= strmake_root(root, name->m_db.str, name->m_db.length);
m_name.length= name->m_name.length;
m_name.str= strmake_root(root, name->m_name.str, name->m_name.length);
if (name->m_qname.length == 0)
name->init_qname(thd);
}
else if (thd->db)
{
m_db.length= thd->db_length;
m_db.str= strmake_root(root, thd->db, m_db.length);
}
DBUG_PRINT("m_db", ("len=%d db=%s",m_db.length, m_db.str));
DBUG_PRINT("m_name", ("len=%d name=%s",m_name.length, m_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;
m_body.length= thd->lex->ptr - m_body_begin;
// Trim nuls at the end
while (m_body.length && m_body_begin[m_body.length-1] == '\0')
m_body.length--;
m_body.str= strmake_root(root, (char *)m_body_begin, m_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(&ltime, 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(&ltime, TIME_to_timestamp(thd,&ltime, &not_used));
m_execute_at= ltime;
DBUG_RETURN(0);
}
/*
Set time for execution for transient events.
SYNOPSIS
event_timed::init_interval()
expr how much?
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 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);
m_expr= tmp;
m_interval= 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 *starts)
{
my_bool not_used;
TIME ltime;
my_time_t my_time_tmp;
DBUG_ENTER("event_timed::init_starts");
if (starts->fix_fields(thd, &starts))
DBUG_RETURN(EVEX_PARSE_ERROR);
if (starts->val_int() == MYSQL_TIMESTAMP_ERROR)
DBUG_RETURN(EVEX_BAD_PARAMS);
if ((not_used= starts->get_date(&ltime, 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(&ltime, TIME_to_timestamp(thd,&ltime, &not_used));
m_starts= ltime;
DBUG_RETURN(0);
}
/*
Set deactivation time.
SYNOPSIS
event_timed::init_ends()
thd THD
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 *ends)
{
TIME ltime;
my_time_t my_time_tmp;
my_bool not_used;
DBUG_ENTER("event_timed::init_ends");
if (ends->fix_fields(thd, &ends))
DBUG_RETURN(EVEX_PARSE_ERROR);
// the field was already fixed in init_ends
if ((not_used= ends->get_date(&ltime, 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(&ltime, TIME_to_timestamp(thd, &ltime, &not_used));
if (m_starts.year && my_time_compare(&m_starts, &ltime) != -1)
DBUG_RETURN(EVEX_BAD_PARAMS);
m_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 *comment)
{
DBUG_ENTER("event_timed::init_comment");
MEM_ROOT *root= thd->mem_root;
m_comment.length= comment->length;
m_comment.str= strmake_root(root, comment->str, comment->length);
DBUG_PRINT("m_comment", ("len=%d",m_comment.length));
DBUG_VOID_RETURN;
}
/*
Inits definer (m_definer_user and m_definer_host) during
parsing.
SYNOPSIS
event_timed::init_definer()
*/
int
event_timed::init_definer(THD *thd)
{
DBUG_ENTER("event_timed::init_definer");
m_definer_user.str= strdup_root(thd->mem_root, thd->security_ctx->priv_user);
m_definer_user.length= strlen(thd->security_ctx->priv_user);
m_definer_host.str= strdup_root(thd->mem_root, thd->security_ctx->priv_host);
m_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()
*/
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->m_db.str= get_field(mem_root,
table->field[EVEX_FIELD_DB])) == NULL)
goto error;
et->m_db.length= strlen(et->m_db.str);
if ((et->m_name.str= get_field(mem_root,
table->field[EVEX_FIELD_NAME])) == NULL)
goto error;
et->m_name.length= strlen(et->m_name.str);
if ((et->m_body.str= get_field(mem_root,
table->field[EVEX_FIELD_BODY])) == NULL)
goto error;
et->m_body.length= strlen(et->m_body.str);
if ((et->m_definer.str= get_field(mem_root,
table->field[EVEX_FIELD_DEFINER])) == NullS)
goto error;
et->m_definer.length= strlen(et->m_definer.str);
ptr= strchr(et->m_definer.str, '@');
if (! ptr)
ptr= et->m_definer.str; // Weird, isn't it?
len= ptr - et->m_definer.str;
et->m_definer_user.str= strmake_root(mem_root, et->m_definer.str, len);
et->m_definer_user.length= len;
len= et->m_definer.length - len - 1; //1 is because of @
et->m_definer_host.str= strmake_root(mem_root, ptr + 1, len);//1: because of @
et->m_definer_host.length= len;
res1= table->field[EVEX_FIELD_STARTS]->
get_date(&et->m_starts, TIME_NO_ZERO_DATE);
res2= table->field[EVEX_FIELD_ENDS]->
get_date(&et->m_ends, TIME_NO_ZERO_DATE);
et->m_expr= 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->m_expr && table->field[EVEX_FIELD_EXECUTE_AT]->
get_date(&et->m_execute_at, TIME_NO_ZERO_DATE))
goto error;
/*
In DB the values start from 1 but enum interval_type starts
from 0
*/
et->m_interval= (interval_type)
((ulonglong) table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->val_int() - 1);
et->m_modified= table->field[EVEX_FIELD_CREATED]->val_int();
et->m_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() m_last_executed gets time_now
and not m_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(&m_last_executed, MYSQL_TIMESTAMP_DATETIME);
#ifdef ANDREY_0
table->field[EVEX_FIELD_LAST_EXECUTED]->
get_date(&et->m_last_executed, TIME_NO_ZERO_DATE);
#endif
m_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->m_name.str, ptr));
et->m_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->m_on_completion= (ptr[0]=='D'? MYSQL_EVENT_ON_COMPLETION_DROP:
MYSQL_EVENT_ON_COMPLETION_PRESERVE);
et->m_comment.str= get_field(mem_root, table->field[EVEX_FIELD_COMMENT]);
if (et->m_comment.str != NullS)
et->m_comment.length= strlen(et->m_comment.str);
else
et->m_comment.length= 0;
DBUG_RETURN(0);
error:
DBUG_RETURN(EVEX_GET_FIELD_FAILED);
}
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 (m_status == MYSQL_EVENT_DISABLED)
{
DBUG_PRINT("compute_next_execution_time",
("Event %s is DISABLED", m_name.str));
goto ret;
}
//if one-time no need to do computation
if (!m_expr)
{
//let's check whether it was executed
if (m_last_executed.year)
{
DBUG_PRINT("compute_next_execution_time",
("One-time event %s was already executed", m_name.str));
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
{
DBUG_PRINT("compute_next_execution_time",
("One-time event will be dropped."));
m_dropped= true;
}
m_status= MYSQL_EVENT_DISABLED;
m_status_changed= true;
}
goto ret;
}
time(&now);
my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
/*
sql_print_information("[%s.%s]", m_db.str, m_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("m_starts : [%d-%d-%d %d:%d:%d ]", m_starts.year, m_starts.month, m_starts.day, m_starts.hour, m_starts.minute, m_starts.second);
sql_print_information("m_ends : [%d-%d-%d %d:%d:%d ]", m_ends.year, m_ends.month, m_ends.day, m_ends.hour, m_ends.minute, m_ends.second);
sql_print_information("m_last_ex: [%d-%d-%d %d:%d:%d ]", m_last_executed.year, m_last_executed.month, m_last_executed.day, m_last_executed.hour, m_last_executed.minute, m_last_executed.second);
*/
//if time_now is after m_ends don't execute anymore
if (m_ends.year && (tmp= my_time_compare(&m_ends, &time_now)) == -1)
{
// time_now is after m_ends. don't execute anymore
set_zero_time(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
m_dropped= true;
m_status= MYSQL_EVENT_DISABLED;
m_status_changed= true;
goto ret;
}
/*
Here time_now is before or equals m_ends if the latter is set.
Let's check whether time_now is before m_starts.
If so schedule for m_starts
*/
if (m_starts.year && (tmp= my_time_compare(&time_now, &m_starts)) < 1)
{
if (tmp == 0 && my_time_compare(&m_starts, &m_last_executed) == 0)
{
/*
time_now = m_starts = m_last_executed
do nothing or we will schedule for second time execution at m_starts.
*/
}
else
{
//m_starts is in the future
//time_now before m_starts. Scheduling for m_starts
m_execute_at= m_starts;
goto ret;
}
}
if (m_starts.year && m_ends.year)
{
/*
Both m_starts and m_ends are set and time_now is between them (incl.)
If m_last_executed is set then increase with m_expr. The new TIME is
after m_ends set m_execute_at to 0. And check for m_on_completion
If not set then schedule for now.
*/
if (!m_last_executed.year)
m_execute_at= time_now;
else
{
my_time_t last, ll_ends;
// There was previous execution
last= sec_since_epoch_TIME(&m_last_executed) + m_expr;
ll_ends= sec_since_epoch_TIME(&m_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(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
m_dropped= true;
}
else
my_tz_UTC->gmt_sec_to_TIME(&m_execute_at, last);
}
goto ret;
}
else if (!m_starts.year && !m_ends.year)
{
// both m_starts and m_ends are not set, se we schedule for the next
// based on m_last_executed
if (!m_last_executed.year)
//m_last_executed not set. Schedule the event for now
m_execute_at= time_now;
else
//ToDo Andrey: maybe check for error here?
my_tz_UTC->gmt_sec_to_TIME(&m_execute_at,
sec_since_epoch_TIME(&m_last_executed) + m_expr);
goto ret;
}
else
{
//either m_starts or m_ends is set
if (m_starts.year)
{
/*
- m_starts is set.
- m_starts is not in the future according to check made before
Hence schedule for m_starts + m_expr in case m_last_executed
is not set, otherwise to m_last_executed + m_expr
*/
my_time_t last;
//convert either m_last_executed or m_starts to seconds
if (m_last_executed.year)
last= sec_since_epoch_TIME(&m_last_executed) + m_expr;
else
last= sec_since_epoch_TIME(&m_starts);
//now convert back to TIME
//ToDo Andrey: maybe check for error here?
my_tz_UTC->gmt_sec_to_TIME(&m_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_expr.
If m_last_executed is not set then schedule for now
*/
my_time_t last, ll_ends;
if (!m_last_executed.year)
m_execute_at= time_now;
else
{
last= sec_since_epoch_TIME(&m_last_executed);
ll_ends= sec_since_epoch_TIME(&m_ends);
last+= m_expr;
//now convert back to TIME
//ToDo Andrey: maybe check for error here?
if (ll_ends < last)
{
set_zero_time(&m_execute_at, MYSQL_TIMESTAMP_DATETIME);
if (m_on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
m_dropped= true;
}
else
my_tz_UTC->gmt_sec_to_TIME(&m_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);
m_last_executed= time_now; // was m_execute_at
#ifdef ANDREY_0
m_last_executed= m_execute_at;
#endif
m_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;
int ret= 0;
bool opened;
DBUG_ENTER("event_timed::update_time_fields");
DBUG_PRINT("enter", ("name: %*s", m_name.length, m_name.str));
//no need to update if nothing has changed
if (!(m_status_changed || m_last_executed_changed))
goto done;
if (!(table= EVEX_OPEN_TABLE_FOR_UPDATE()))
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
if ((ret= sp_db_find_routine_aux(thd, 0/*notype*/, m_db, m_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 (m_last_executed_changed)
{
table->field[EVEX_FIELD_LAST_EXECUTED]->set_notnull();
table->field[EVEX_FIELD_LAST_EXECUTED]->store_time(&m_last_executed,
MYSQL_TIMESTAMP_DATETIME);
m_last_executed_changed= false;
}
if (m_status_changed)
{
table->field[EVEX_FIELD_STATUS]->set_notnull();
table->field[EVEX_FIELD_STATUS]->store((longlong)m_status);
m_status_changed= false;
}
if ((table->file->update_row(table->record[1],table->record[0])))
ret= EVEX_WRITE_ROW_FAILED;
done:
close_thread_tables(thd);
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 ") + m_db.length + strlen(".") + m_name.length +
strlen(" ON SCHEDULE ") + strlen("EVERY 5 MINUTE ")
/*
+ strlen("ON COMPLETION ")
+ (m_on_completion==MYSQL_EVENT_ON_COMPLETION_DROP?
strlen("NOT PRESERVE "):strlen("PRESERVE "))
+ (m_status==MYSQL_EVENT_ENABLED?
strlen("ENABLE "):strlen("DISABLE "))
+ strlen("COMMENT \"") + m_comment.length + strlen("\" ")
*/
+ strlen("DO ") +
+ m_body.length + strlen(";");
ret= dst= (char*) alloc_root(thd->mem_root, len);
memcpy(dst, "CREATE EVENT ", tmp_len= strlen("CREATE EVENT "));
dst+= tmp_len;
memcpy(dst, m_db.str, tmp_len=m_db.length);
dst+= tmp_len;
memcpy(dst, ".", tmp_len= strlen("."));
dst+= tmp_len;
memcpy(dst, m_name.str, tmp_len= m_name.length);
dst+= tmp_len;
memcpy(dst, " ON SCHEDULE ", tmp_len= strlen(" ON SCHEDULE "));
dst+= tmp_len;
memcpy(dst, "EVERY 5 MINUTE ", tmp_len= strlen("EVERY 5 MINUTE "));
dst+= tmp_len;
/*
memcpy(dst, "ON COMPLETION ", tmp_len =strlen("ON COMPLETION "));
dst+= tmp_len;
memcpy(dst, (m_on_completion==MYSQL_EVENT_ON_COMPLETION_DROP?
"NOT PRESERVE ":"PRESERVE "),
tmp_len =(m_on_completion==MYSQL_EVENT_ON_COMPLETION_DROP? 13:9));
dst+= tmp_len;
memcpy(dst, (m_status==MYSQL_EVENT_ENABLED?
"ENABLE ":"DISABLE "),
tmp_len= (m_status==MYSQL_EVENT_ENABLED? 8:9));
dst+=tmp_len;
memcpy(dst, "COMMENT \"", tmp_len= strlen("COMMENT \""));
dst+= tmp_len;
memcpy(dst, m_comment.str, tmp_len= m_comment.length);
dst+= tmp_len;
memcpy(dst, "\" ", tmp_len=2);
dst+= tmp_len;
*/
memcpy(dst, "DO ", tmp_len=3);
dst+= tmp_len;
memcpy(dst, m_body.str, tmp_len= m_body.length);
dst+= tmp_len;
memcpy(dst, ";", 1);
++dst;
*dst= '\0';
*length= len;
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 (!m_sphead && (ret= compile(thd, mem_root)))
goto done;
ret= m_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 m_sphead if allocated on another mem_root
if (mem_root && m_sphead)
{
delete m_sphead;
m_sphead= 0;
}
DBUG_RETURN(ret);
}
int
event_timed::compile(THD *thd, MEM_ROOT *mem_root)
{
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;
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= m_db.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;
DBUG_RETURN(-1);
}
m_sphead= lex.sphead;
m_sphead->m_db= m_db;
//copy also chistics since they will vanish otherwise we get 0x0 pointer
// Todo : Handle sql_mode !!
m_sphead->set_definer(m_definer.str, m_definer.length);
m_sphead->set_info(0, 0, &lex.sp_chistics, 0/*sql_mode*/);
m_sphead->optimize();
lex_end(&lex);
thd->lex= old_lex;
thd->query= old_query;
thd->query_length= old_query_len;
thd->db= old_db;
/*
Change the memory root for the execution time.
*/
if (mem_root)
thd->mem_root= tmp_mem_root;
DBUG_RETURN(0);
}
......@@ -4807,7 +4807,7 @@ 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-executor", OPT_EVENT_EXECUTOR, "Print a symbolic stack trace on failure.",
{"event-scheduler", OPT_EVENT_EXECUTOR, "Enable/disable the event scheduler.",
(gptr*) &opt_event_executor, (gptr*) &opt_event_executor, 0, GET_BOOL, NO_ARG,
1/*default*/, 0/*min-value*/, 1/*max-value*/, 0, 0, 0},
{"exit-info", 'T', "Used for debugging; Use at your own risk!", 0, 0, 0,
......
......@@ -207,7 +207,7 @@ 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_bool_ptr sys_event_executor("event_executor",
sys_var_bool_ptr sys_event_executor("event_scheduler",
&event_executor_running_global_var);
sys_var_long_ptr sys_expire_logs_days("expire_logs_days",
&expire_logs_days);
......
......@@ -5730,7 +5730,7 @@ ER_EVENT_DOES_NOT_EXIST
ER_EVENT_CANT_ALTER
eng "Failed to alter event %s"
ER_EVENT_DROP_FAILED
eng "Failed to DROP %s %s"
eng "Failed to drop %s"
ER_EVENT_INTERVAL_NOT_POSITIVE
eng "INTERVAL must be positive"
ER_EVENT_ENDS_BEFORE_STARTS
......
......@@ -958,10 +958,10 @@ int sp_head::execute(THD *thd)
DBUG_ASSERT(!(m_flags & IS_INVOKED));
m_flags|= IS_INVOKED;
m_first_instance->m_first_free_instance= m_next_cached_sp;
DBUG_PRINT("info", ("first free for 0x%lx ++: 0x%lx->0x%lx, level: %lu, flags %x",
(ulong)m_first_instance, this, m_next_cached_sp,
m_next_cached_sp->m_recursion_level,
m_next_cached_sp->m_flags));
// DBUG_PRINT("info", ("first free for 0x%lx ++: 0x%lx->0x%lx, level: %lu, flags %x",
// (ulong)m_first_instance, this, m_next_cached_sp,
// m_next_cached_sp->m_recursion_level,
// m_next_cached_sp->m_flags));
/*
Check that if there are not any instances after this one then
pointer to the last instance points on this instance or if there are
......
......@@ -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
......
......@@ -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);
......
......@@ -57,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 | \
......
......@@ -3675,9 +3675,6 @@ end_with_restore_list:
}
case SQLCOM_CREATE_EVENT:
{
if (check_global_access(thd, EVENT_ACL))
break;
DBUG_ASSERT(lex->et);
if (! lex->et->m_db.str)
{
......@@ -3686,7 +3683,9 @@ end_with_restore_list:
lex->et= 0;
goto error;
}
if (check_access(thd, EVENT_ACL, lex->et->m_db.str, 0, 0, 0,
is_schema_db(lex->et->m_db.str)))
break;
int result;
uint create_options= lex->create_info.options;
res= (result= evex_create_event(thd, lex->et, create_options));
......@@ -3716,9 +3715,6 @@ end_with_restore_list:
}
case SQLCOM_ALTER_EVENT:
{
if (check_global_access(thd, EVENT_ACL))
break;
DBUG_ASSERT(lex->et);
if (! lex->et->m_db.str)
{
......@@ -3727,6 +3723,9 @@ end_with_restore_list:
lex->et= 0;
goto error;
}
if (check_access(thd, EVENT_ACL, lex->et->m_db.str, 0, 0, 0,
is_schema_db(lex->et->m_db.str)))
break;
int result;
res= (result= evex_update_event(thd, lex->spname, lex->et));
......@@ -3735,10 +3734,10 @@ end_with_restore_list:
send_ok(thd, 1);
break;
case EVEX_KEY_NOT_FOUND:
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), lex->et->m_qname.str);
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), lex->et->m_name.str);
break;
default:
my_error(ER_EVENT_CANT_ALTER, MYF(0), lex->et->m_qname.str);
my_error(ER_EVENT_CANT_ALTER, MYF(0), lex->et->m_name.str);
break;
}
delete lex->et;
......@@ -3754,9 +3753,6 @@ end_with_restore_list:
}
case SQLCOM_DROP_EVENT:
{
if (check_global_access(thd, EVENT_ACL))
break;
DBUG_ASSERT(lex->et);
if (! lex->et->m_db.str)
{
......@@ -3765,6 +3761,9 @@ end_with_restore_list:
lex->et= 0;
goto error;
}
if (check_access(thd, EVENT_ACL, lex->et->m_db.str, 0, 0, 0,
is_schema_db(lex->et->m_db.str)))
break;
int result;
res= (result= evex_drop_event(thd, lex->et, lex->drop_if_exists));
......@@ -3773,10 +3772,10 @@ end_with_restore_list:
send_ok(thd, 1);
break;
case EVEX_KEY_NOT_FOUND:
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), lex->et->m_qname.str);
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), lex->et->m_name.str);
break;
default:
my_error(ER_EVENT_DROP_FAILED, MYF(0), lex->et->m_qname.str);
my_error(ER_EVENT_DROP_FAILED, MYF(0), lex->et->m_name.str);
break;
}
delete lex->et;
......@@ -3786,7 +3785,8 @@ end_with_restore_list:
}
case SQLCOM_SHOW_CREATE_EVENT:
{
if (check_global_access(thd, EVENT_ACL))
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)
......@@ -3794,6 +3794,7 @@ end_with_restore_list:
my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str);
goto error;
}
/* TODO : Implement it */
send_ok(thd, 1);
break;
}
......
......@@ -1261,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);
}
'('
......@@ -1294,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
......@@ -1314,32 +1314,35 @@ create:
| CREATE EVENT_SYM opt_if_not_exists sp_name
{
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->create_info.options=$3;
et= new event_timed();// implicitly calls event_timed::init()
lex->et = et;
lex->create_info.options= $3;
if (!lex->et_compile_phase)
et->init_name(YYTHD, $4);
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 ';'.
*/
et->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES;
$<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES);
lex->sphead= 0;
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
......@@ -1347,9 +1350,14 @@ create:
ev_comment
DO_SYM ev_sql_stmt
{
LEX *lex=Lex;
/*
sql_command is set here because some rules in ev_sql_stmt
can overwrite it
*/
// Restore flag if it was cleared above
YYTHD->client_capabilities |= $<ulong_num>5;
lex->sql_command= SQLCOM_CREATE_EVENT;
Lex->sql_command= SQLCOM_CREATE_EVENT;
}
;
......@@ -1396,14 +1404,14 @@ ev_status: /* empty */
{
LEX *lex=Lex;
if (!lex->et_compile_phase)
lex->et->set_event_status(1);
lex->et->m_status= MYSQL_EVENT_ENABLED;
}
| DISABLE_SYM
{
LEX *lex=Lex;
if (!lex->et_compile_phase)
lex->et->set_event_status(0);
lex->et->m_status= MYSQL_EVENT_DISABLED;
}
;
ev_starts: /* empty */
......@@ -1438,13 +1446,13 @@ ev_on_completion: /* empty */
{
LEX *lex=Lex;
if (!lex->et_compile_phase)
lex->et->set_on_completion_drop(false);
lex->et->m_on_completion= MYSQL_EVENT_ON_COMPLETION_PRESERVE;
}
| ON COMPLETION_SYM NOT_SYM PRESERVE_SYM
{
LEX *lex=Lex;
if (!lex->et_compile_phase)
lex->et->set_on_completion_drop(true);
lex->et->m_on_completion= MYSQL_EVENT_ON_COMPLETION_DROP;
}
;
ev_comment: /* empty */
......@@ -1490,10 +1498,6 @@ ev_sql_stmt:
lex->sp_chistics.suid= SP_IS_SUID;//always the definer!
// Restore flag if it was cleared above
if (lex->et->m_old_cmq)
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
if (!lex->et_compile_phase)
{
lex->et->init_body(YYTHD);
......@@ -1577,7 +1581,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;
}
......@@ -1656,9 +1660,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);
}
;
......@@ -4194,11 +4198,15 @@ alter:
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
lex->spname= 0;//defensive programming
et= new event_timed();// implicitly calls event_timed::init()
lex->et = et;
......@@ -4209,7 +4217,7 @@ alter:
stored procedure, otherwise yylex will chop it into pieces
at each ';'.
*/
et->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
$<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES;
/*
......@@ -4225,8 +4233,12 @@ alter:
ev_comment
ev_opt_sql_stmt
{
LEX *lex=Lex;
lex->sql_command= SQLCOM_ALTER_EVENT;
/*
sql_command is set here because some rules in ev_sql_stmt
can overwrite it
*/
YYTHD->client_capabilities |= $<ulong_num>3;
Lex->sql_command= SQLCOM_ALTER_EVENT;
}
;
......@@ -6953,18 +6965,21 @@ drop:
| DROP EVENT_SYM if_exists sp_name
{
LEX *lex=Lex;
event_timed *et;
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;
}
et= new event_timed;
lex->et = et;
et->init_name(YYTHD, $4);
if (!(lex->et= new event_timed()))
YYABORT;
lex->et->init_name(YYTHD, $4);
lex->sql_command = SQLCOM_DROP_EVENT;
lex->drop_if_exists= $3;
......@@ -7623,10 +7638,8 @@ show_param:
}
| CREATE EVENT_SYM sp_name
{
LEX *lex= Lex;
lex->sql_command = SQLCOM_SHOW_CREATE_EVENT;
lex->spname= $3;
Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT;
Lex->spname= $3;
};
;
......@@ -10076,7 +10089,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));
......@@ -10091,8 +10104,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"))
......
......@@ -64,7 +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();
my_time_t sec_since_epoch_TIME(TIME *t);
extern my_time_t sec_since_epoch_TIME(TIME *t);
extern TABLE_LIST fake_time_zone_tables_list;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment