Commit b67555d5 authored by unknown's avatar unknown

Merge ahristov@bk-internal.mysql.com:/home/bk/mysql-5.1-wl1034

into lmy004.:/work/mysql-5.1-tt-copy-works


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