summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorunknown <kroki/tomash@moonlight.home>2007-03-16 17:31:07 +0300
committerunknown <kroki/tomash@moonlight.home>2007-03-16 17:31:07 +0300
commit086fba76272c2350b71c93e4343ab5fb3ea5d7da (patch)
tree8922072c4b428d1e321673d0ec9e460b316a9c07 /sql
parent783b7748370880c8ba4ff80e76d27209efb369f6 (diff)
downloadmariadb-git-086fba76272c2350b71c93e4343ab5fb3ea5d7da.tar.gz
BUG#16420: Events: timestamps become UTC
BUG#26429: SHOW CREATE EVENT is incorrect for an event that STARTS NOW() BUG#26431: Impossible to re-create an event from backup if its STARTS clause is in the past WL#3698: Events: execution in local time zone The problem was that local times specified by the user in AT, STARTS and ENDS of CREATE EVENT/ALTER EVENT statement were converted to UTC, and the original time zone was forgotten. This way, event scheduler couldn't honor Daylight Saving Time shifts, and times shown to the user were also in UTC. Additionally, CREATE EVENT didn't allow times in the past, thus preventing straightforward event restoration from old backups. This patch reworks event scheduler time computations, performing them in the time zone associated with the event. Also it allows times to be in the past. The patch adds time_zone column to mysql.event table. NOTE: The patch is almost final, but the bug#9953 should be pushed first. client/mysqldump.c: Before every CREATE EVENT, output its time zone. mysql-test/include/wait_condition.inc: Add optional $wait_timeout parameter. mysql-test/lib/init_db.sql: Add time_zone column. mysql-test/r/events.result: Update result. mysql-test/r/events_bugs.result: Update result. mysql-test/r/events_grant.result: Update result. mysql-test/r/events_restart_phase1.result: Update result. mysql-test/r/events_scheduling.result: Update result. mysql-test/r/mysqldump.result: Update result. mysql-test/r/ps.result: Update result. mysql-test/r/system_mysql_db.result: Update result. mysql-test/t/events.test: Remove STARTS from the result, as it depends on current time. mysql-test/t/events_bugs.test: Time in the past is no longer an error. mysql-test/t/events_restart_phase1.test: Fill new column 'time_zone' in mysql.event. mysql-test/t/events_scheduling.test: Cleanup: disable event scheduler. scripts/mysql_create_system_tables.sh: Add new column 'time_zone' to mysql.event. scripts/mysql_fix_privilege_tables.sql: Add new column 'time_zone' to mysql.event. sql/event_data_objects.cc: The essence of the change is the following: - for internal times use my_time_t instead of TIME. Assignment and comparison is done now on plain numbers. - in init_execute_at(), init_starts(), init_ends() convert given time to number of seconds since Epoch (aka Unix time, in UTC). - handle time_zone field loading and storing. - in get_next_time(), Unix time is converted back to event time zone, interval is added, and the result is converted to UTC again. - fix Event_timed::get_create_event() to report STARTS and ENDS. - before executing the event body we set thread time zone to the event time zone. sql/event_data_objects.h: Add time_zone member to Event_basic class. Store internal times in my_time_t (number of seconds since Epoch), rather than in broken down TIME structure. sql/event_db_repository.cc: Add time_zone column handling. Give a warning and do not create an event if its execution time is in the past, and ON COMPLETION NOT PRESERVE is set, because such an event should be dropped by that time. Also, do not allow ALTER EVENT to set execution time in the past when ON COMPLETION NOT PRESERVE is set. sql/event_db_repository.h: Add enum member for new time zone column. sql/event_queue.cc: Replace handling of broken down times with simple handling of my_time_t. sql/event_queue.h: Store internal times in my_time_t (number of seconds since Epoch), rather than in broken down TIME structure. sql/event_scheduler.cc: Add TODO comment. sql/events.cc: Send time_zone column for SHOW CREATE EVENT. sql/share/errmsg.txt: Update error message, and add two more errors. sql/sql_show.cc: Add TIME_ZONE column to the output of SHOW EVENTS. mysql-test/r/events_time_zone.result: BitKeeper file /home/tomash/src/mysql_ab/mysql-5.1-wl3698/mysql-test/r/events_time_zone.result mysql-test/t/events_time_zone.test: BitKeeper file /home/tomash/src/mysql_ab/mysql-5.1-wl3698/mysql-test/t/events_time_zone.test
Diffstat (limited to 'sql')
-rw-r--r--sql/event_data_objects.cc579
-rw-r--r--sql/event_data_objects.h35
-rw-r--r--sql/event_db_repository.cc43
-rw-r--r--sql/event_db_repository.h1
-rw-r--r--sql/event_queue.cc43
-rw-r--r--sql/event_queue.h2
-rw-r--r--sql/event_scheduler.cc8
-rw-r--r--sql/events.cc13
-rw-r--r--sql/share/errmsg.txt7
-rw-r--r--sql/sql_show.cc24
10 files changed, 471 insertions, 284 deletions
diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc
index dad8aeb2e20..36f20c34200 100644
--- a/sql/event_data_objects.cc
+++ b/sql/event_data_objects.cc
@@ -101,7 +101,7 @@ Event_parse_data::new_instance(THD *thd)
*/
Event_parse_data::Event_parse_data()
- :on_completion(ON_COMPLETION_DROP), status(ENABLED),
+ :on_completion(ON_COMPLETION_DROP), status(ENABLED), do_not_create(FALSE),
item_starts(NULL), item_ends(NULL), item_execute_at(NULL),
starts_null(TRUE), ends_null(TRUE), execute_at_null(TRUE),
item_expression(NULL), expression(0)
@@ -109,9 +109,7 @@ Event_parse_data::Event_parse_data()
DBUG_ENTER("Event_parse_data::Event_parse_data");
/* Actually in the parser STARTS is always set */
- set_zero_time(&starts, MYSQL_TIMESTAMP_DATETIME);
- set_zero_time(&ends, MYSQL_TIMESTAMP_DATETIME);
- set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ starts= ends= execute_at= 0;
body.str= comment.str= NULL;
body.length= comment.length= 0;
@@ -224,6 +222,55 @@ Event_parse_data::init_body(THD *thd)
/*
+ This function is called on CREATE EVENT or ALTER EVENT. When either
+ ENDS or AT is in the past, we are trying to create an event that
+ will never be executed. If it has ON COMPLETION NOT PRESERVE
+ (default), then it would normally be dropped already, so on CREATE
+ EVENT we give a warning, and do not create anyting. On ALTER EVENT
+ we give a error, and do not change the event.
+
+ If the event has ON COMPLETION PRESERVE, then we see if the event is
+ created or altered to the ENABLED (default) state. If so, then we
+ give a warning, and change the state to DISABLED.
+
+ Otherwise it is a valid event in ON COMPLETION PRESERVE DISABLE
+ state.
+*/
+
+void
+Event_parse_data::check_if_in_the_past(THD *thd, my_time_t ltime_utc)
+{
+ if (ltime_utc >= (my_time_t) thd->query_start())
+ return;
+
+ if (on_completion == ON_COMPLETION_DROP)
+ {
+ switch (thd->lex->sql_command) {
+ case SQLCOM_CREATE_EVENT:
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_EVENT_CANNOT_CREATE_IN_THE_PAST,
+ ER(ER_EVENT_CANNOT_CREATE_IN_THE_PAST));
+ break;
+ case SQLCOM_ALTER_EVENT:
+ my_error(ER_EVENT_CANNOT_ALTER_IN_THE_PAST, MYF(0));
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+
+ do_not_create= TRUE;
+ }
+ else if (status == ENABLED)
+ {
+ status= DISABLED;
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_EVENT_EXEC_TIME_IN_THE_PAST,
+ ER(ER_EVENT_EXEC_TIME_IN_THE_PAST));
+ }
+}
+
+
+/*
Sets time for execution for one-time event.
SYNOPSIS
@@ -240,8 +287,7 @@ Event_parse_data::init_execute_at(THD *thd)
{
my_bool not_used;
TIME ltime;
- my_time_t t;
- TIME time_tmp;
+ my_time_t ltime_utc;
DBUG_ENTER("Event_parse_data::init_execute_at");
@@ -256,35 +302,20 @@ Event_parse_data::init_execute_at(THD *thd)
(starts_null && ends_null)));
DBUG_ASSERT(starts_null && ends_null);
- /* 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 ((not_used= item_execute_at->get_date(&ltime, TIME_NO_ZERO_DATE)))
goto wrong_value;
- if (TIME_to_ulonglong_datetime(&ltime) <
- TIME_to_ulonglong_datetime(&time_tmp))
- {
- my_error(ER_EVENT_EXEC_TIME_IN_THE_PAST, MYF(0));
- DBUG_RETURN(ER_WRONG_VALUE);
- }
-
- /*
- This may result in a 1970-01-01 date if ltime is > 2037-xx-xx.
- CONVERT_TZ has similar problem.
- mysql_priv.h currently lists
- #define TIMESTAMP_MAX_YEAR 2038 (see TIME_to_timestamp())
- */
- my_tz_UTC->gmt_sec_to_TIME(&ltime,t=TIME_to_timestamp(thd,&ltime,&not_used));
- if (!t)
+ ltime_utc= TIME_to_timestamp(thd,&ltime,&not_used);
+ if (!ltime_utc)
{
DBUG_PRINT("error", ("Execute AT after year 2037"));
goto wrong_value;
}
+ check_if_in_the_past(thd, ltime_utc);
+
execute_at_null= FALSE;
- execute_at= ltime;
+ execute_at= ltime_utc;
DBUG_RETURN(0);
wrong_value:
@@ -424,8 +455,8 @@ int
Event_parse_data::init_starts(THD *thd)
{
my_bool not_used;
- TIME ltime, time_tmp;
- my_time_t t;
+ TIME ltime;
+ my_time_t ltime_utc;
DBUG_ENTER("Event_parse_data::init_starts");
if (!item_starts)
@@ -437,36 +468,15 @@ Event_parse_data::init_starts(THD *thd)
if ((not_used= item_starts->get_date(&ltime, TIME_NO_ZERO_DATE)))
goto wrong_value;
- /*
- Let's check whether time is in the past.
- Note: This call is not post year 2038 safe. That's because
- thd->query_start() is of time_t, while gmt_sec_to_TIME()
- wants my_time_t. In the case time_t is larger than my_time_t
- an overflow might happen and events subsystem will not work as
- expected.
- */
- thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp,
- (my_time_t) thd->query_start());
-
- DBUG_PRINT("info",("now: %ld starts: %ld",
- (long) TIME_to_ulonglong_datetime(&time_tmp),
- (long) TIME_to_ulonglong_datetime(&ltime)));
- if (TIME_to_ulonglong_datetime(&ltime) <
- TIME_to_ulonglong_datetime(&time_tmp))
+ ltime_utc= TIME_to_timestamp(thd, &ltime, &not_used);
+ if (!ltime_utc)
goto wrong_value;
- /*
- Again, after 2038 this code won't work. As
- mysql_priv.h currently lists
- #define TIMESTAMP_MAX_YEAR 2038 (see TIME_to_timestamp())
- */
- my_tz_UTC->gmt_sec_to_TIME(&ltime,t=TIME_to_timestamp(thd, &ltime,
- &not_used));
- if (!t)
- goto wrong_value;
+ DBUG_PRINT("info",("now: %ld starts: %ld",
+ (long) thd->query_start(), (long) ltime_utc));
- starts= ltime;
starts_null= FALSE;
+ starts= ltime_utc;
DBUG_RETURN(0);
wrong_value:
@@ -498,9 +508,9 @@ wrong_value:
int
Event_parse_data::init_ends(THD *thd)
{
- TIME ltime, ltime_now;
my_bool not_used;
- my_time_t t;
+ TIME ltime;
+ my_time_t ltime_utc;
DBUG_ENTER("Event_parse_data::init_ends");
if (!item_ends)
@@ -513,34 +523,19 @@ Event_parse_data::init_ends(THD *thd)
if ((not_used= item_ends->get_date(&ltime, TIME_NO_ZERO_DATE)))
goto error_bad_params;
- /*
- Again, after 2038 this code won't work. As
- mysql_priv.h currently lists
- #define TIMESTAMP_MAX_YEAR 2038 (see TIME_to_timestamp())
- */
- DBUG_PRINT("info", ("get the UTC time"));
- my_tz_UTC->gmt_sec_to_TIME(&ltime,t=TIME_to_timestamp(thd, &ltime,
- &not_used));
- if (!t)
+ ltime_utc= TIME_to_timestamp(thd, &ltime, &not_used);
+ if (!ltime_utc)
goto error_bad_params;
/* Check whether ends is after starts */
DBUG_PRINT("info", ("ENDS after STARTS?"));
- if (!starts_null && my_time_compare(&starts, &ltime) != -1)
+ if (!starts_null && starts >= ltime_utc)
goto error_bad_params;
- /*
- The parser forces starts to be provided but one day STARTS could be
- set before NOW() and in this case the following check should be done.
- Check whether ENDS is not in the past.
- */
- DBUG_PRINT("info", ("ENDS after NOW?"));
- my_tz_UTC->gmt_sec_to_TIME(&ltime_now, thd->query_start());
- if (my_time_compare(&ltime_now, &ltime) == 1)
- goto error_bad_params;
+ check_if_in_the_past(thd, ltime_utc);
- ends= ltime;
ends_null= FALSE;
+ ends= ltime_utc;
DBUG_RETURN(0);
error_bad_params:
@@ -656,6 +651,7 @@ Event_basic::Event_basic()
init_alloc_root(&mem_root, 256, 512);
dbname.str= name.str= NULL;
dbname.length= name.length= 0;
+ time_zone= NULL;
DBUG_VOID_RETURN;
}
@@ -716,6 +712,16 @@ Event_basic::load_string_fields(Field **fields, ...)
}
+bool
+Event_basic::load_time_zone(THD *thd, const LEX_STRING tz_name)
+{
+ String str(tz_name.str, &my_charset_latin1);
+ time_zone= my_tz_find(thd, &str);
+
+ return (time_zone == NULL);
+}
+
+
/*
Constructor
@@ -730,10 +736,7 @@ Event_queue_element::Event_queue_element():
{
DBUG_ENTER("Event_queue_element::Event_queue_element");
- 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);
+ starts= ends= execute_at= last_executed= 0;
starts_null= ends_null= execute_at_null= TRUE;
DBUG_VOID_RETURN;
@@ -833,7 +836,7 @@ Event_timed::init()
Loads an event's body from a row from mysql.event
SYNOPSIS
- Event_job_data::load_from_row(MEM_ROOT *mem_root, TABLE *table)
+ Event_job_data::load_from_row(THD *thd, TABLE *table)
RETURN VALUE
0 OK
@@ -846,7 +849,7 @@ Event_timed::init()
*/
int
-Event_job_data::load_from_row(TABLE *table)
+Event_job_data::load_from_row(THD *thd, TABLE *table)
{
char *ptr;
uint len;
@@ -858,9 +861,12 @@ Event_job_data::load_from_row(TABLE *table)
if (table->s->fields != ET_FIELD_COUNT)
goto error;
+ LEX_STRING tz_name;
load_string_fields(table->field, ET_FIELD_DB, &dbname, ET_FIELD_NAME, &name,
ET_FIELD_BODY, &body, ET_FIELD_DEFINER, &definer,
- ET_FIELD_COUNT);
+ ET_FIELD_TIME_ZONE, &tz_name, ET_FIELD_COUNT);
+ if (load_time_zone(thd, tz_name))
+ goto error;
ptr= strchr(definer.str, '@');
@@ -887,7 +893,7 @@ error:
Loads an event from a row from mysql.event
SYNOPSIS
- Event_queue_element::load_from_row(MEM_ROOT *mem_root, TABLE *table)
+ Event_queue_element::load_from_row(THD *thd, TABLE *table)
RETURN VALUE
0 OK
@@ -900,10 +906,10 @@ error:
*/
int
-Event_queue_element::load_from_row(TABLE *table)
+Event_queue_element::load_from_row(THD *thd, TABLE *table)
{
char *ptr;
- bool res1, res2;
+ TIME time;
DBUG_ENTER("Event_queue_element::load_from_row");
@@ -913,29 +919,44 @@ Event_queue_element::load_from_row(TABLE *table)
if (table->s->fields != ET_FIELD_COUNT)
goto error;
+ LEX_STRING tz_name;
load_string_fields(table->field, ET_FIELD_DB, &dbname, ET_FIELD_NAME, &name,
- ET_FIELD_DEFINER, &definer, ET_FIELD_COUNT);
+ ET_FIELD_DEFINER, &definer,
+ ET_FIELD_TIME_ZONE, &tz_name, ET_FIELD_COUNT);
+ if (load_time_zone(thd, tz_name))
+ goto error;
starts_null= table->field[ET_FIELD_STARTS]->is_null();
- res1= table->field[ET_FIELD_STARTS]->get_date(&starts, TIME_NO_ZERO_DATE);
+ if (!starts_null)
+ {
+ table->field[ET_FIELD_STARTS]->get_date(&time, TIME_NO_ZERO_DATE);
+ starts= sec_since_epoch_TIME(&time);
+ }
ends_null= table->field[ET_FIELD_ENDS]->is_null();
- res2= table->field[ET_FIELD_ENDS]->get_date(&ends, TIME_NO_ZERO_DATE);
+ if (!ends_null)
+ {
+ table->field[ET_FIELD_ENDS]->get_date(&time, TIME_NO_ZERO_DATE);
+ ends= sec_since_epoch_TIME(&time);
+ }
if (!table->field[ET_FIELD_INTERVAL_EXPR]->is_null())
expression= table->field[ET_FIELD_INTERVAL_EXPR]->val_int();
else
expression= 0;
/*
- If res1 and res2 are TRUE then both fields are empty.
+ If neigher STARTS and ENDS is set, then both fields are empty.
Hence, if ET_FIELD_EXECUTE_AT is empty there is an error.
*/
execute_at_null= table->field[ET_FIELD_EXECUTE_AT]->is_null();
DBUG_ASSERT(!(starts_null && ends_null && !expression && execute_at_null));
- if (!expression &&
- table->field[ET_FIELD_EXECUTE_AT]->get_date(&execute_at,
- TIME_NO_ZERO_DATE))
- goto error;
+ if (!expression && !execute_at_null)
+ {
+ if (table->field[ET_FIELD_EXECUTE_AT]->get_date(&time,
+ TIME_NO_ZERO_DATE))
+ goto error;
+ execute_at= sec_since_epoch_TIME(&time);
+ }
/*
We load the interval type from disk as string and then map it to
@@ -962,11 +983,14 @@ Event_queue_element::load_from_row(TABLE *table)
interval= (interval_type) i;
}
- table->field[ET_FIELD_LAST_EXECUTED]->get_date(&last_executed,
- TIME_NO_ZERO_DATE);
+ if (!table->field[ET_FIELD_LAST_EXECUTED]->is_null())
+ {
+ table->field[ET_FIELD_LAST_EXECUTED]->get_date(&time,
+ TIME_NO_ZERO_DATE);
+ last_executed= sec_since_epoch_TIME(&time);
+ }
last_executed_changed= FALSE;
-
if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS)
goto error;
@@ -992,7 +1016,7 @@ error:
Loads an event from a row from mysql.event
SYNOPSIS
- Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table)
+ Event_timed::load_from_row(THD *thd, TABLE *table)
RETURN VALUE
0 OK
@@ -1005,14 +1029,14 @@ error:
*/
int
-Event_timed::load_from_row(TABLE *table)
+Event_timed::load_from_row(THD *thd, TABLE *table)
{
char *ptr;
uint len;
DBUG_ENTER("Event_timed::load_from_row");
- if (Event_queue_element::load_from_row(table))
+ if (Event_queue_element::load_from_row(thd, table))
goto error;
load_string_fields(table->field, ET_FIELD_BODY, &body, ET_FIELD_COUNT);
@@ -1048,11 +1072,30 @@ error:
/*
- Computes the sum of a timestamp plus interval. Presumed is that at least one
- previous execution has occured.
+ add_interval() adds a specified interval to time 'ltime' in time
+ zone 'time_zone', and returns the result converted to the number of
+ seconds since epoch (aka Unix time; in UTC time zone). Zero result
+ means an error.
+*/
+static
+my_time_t
+add_interval(TIME *ltime, const Time_zone *time_zone,
+ interval_type scale, INTERVAL interval)
+{
+ if (date_add_interval(ltime, scale, interval))
+ return 0;
+
+ my_bool not_used;
+ return time_zone->TIME_to_gmt_sec(ltime, &not_used);
+}
+
+
+/*
+ Computes the sum of a timestamp plus interval.
SYNOPSIS
- get_next_time(TIME *start, int interval_value, interval_type interval)
+ get_next_time()
+ time_zone event time zone
next the sum
start add interval_value to this time
time_now current time
@@ -1069,26 +1112,19 @@ error:
seconds as resolution for computation.
2) In all other cases - MONTH, QUARTER, YEAR we use MONTH as resolution
and PERIOD_DIFF()'s implementation
- 3) We get the difference between time_now and `start`, then divide it
- by the months, respectively seconds and round up. Then we multiply
- monts/seconds by the rounded value and add it to `start` -> we get
- the next execution time.
*/
static
-bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec,
+bool get_next_time(const Time_zone *time_zone, my_time_t *next,
+ my_time_t start, my_time_t time_now,
int i_value, interval_type i_type)
{
- bool ret;
- INTERVAL interval;
- TIME tmp;
- longlong months=0, seconds=0;
DBUG_ENTER("get_next_time");
- DBUG_PRINT("enter", ("start: %lu now: %lu",
- (long) TIME_to_ulonglong_datetime(start),
- (long) TIME_to_ulonglong_datetime(time_now)));
+ DBUG_PRINT("enter", ("start: %lu now: %lu", (long) start, (long) time_now));
- bzero(&interval, sizeof(interval));
+ DBUG_ASSERT(start <= time_now);
+
+ longlong months=0, seconds=0;
switch (i_type) {
case INTERVAL_YEAR:
@@ -1135,84 +1171,151 @@ bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec,
DBUG_ASSERT(0);
}
DBUG_PRINT("info", ("seconds: %ld months: %ld", (long) seconds, (long) months));
+
+ TIME local_start;
+ TIME local_now;
+
+ /* Convert times from UTC to local. */
+ {
+ time_zone->gmt_sec_to_TIME(&local_start, start);
+ time_zone->gmt_sec_to_TIME(&local_now, time_now);
+ }
+
+ INTERVAL interval;
+ bzero(&interval, sizeof(interval));
+ my_time_t next_time= 0;
+
if (seconds)
{
longlong seconds_diff;
long microsec_diff;
+ bool negative= calc_time_diff(&local_now, &local_start, 1,
+ &seconds_diff, &microsec_diff);
+ if (!negative)
+ {
+ /*
+ The formula below returns the interval that, when added to
+ local_start, will always give the time in the future.
+ */
+ interval.second= seconds_diff - seconds_diff % seconds + seconds;
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_SECOND, interval);
+ if (next_time == 0)
+ goto done;
+ }
- if (calc_time_diff(time_now, start, 1, &seconds_diff, &microsec_diff))
+ if (next_time <= time_now)
{
- DBUG_PRINT("error", ("negative difference"));
- DBUG_ASSERT(0);
+ /*
+ If 'negative' is true above, then 'next_time == 0', and
+ 'next_time <= time_now' is also true. If negative is false,
+ then next_time was set, but perhaps to the value that is less
+ then time_now. See below for elaboration.
+ */
+ DBUG_ASSERT(negative || next_time > 0);
+
+ /*
+ If local_now < local_start, i.e. STARTS time is in the future
+ according to the local time (it always in the past according
+ to UTC---this is a prerequisite of this function), then
+ STARTS is almost always in the past according to the local
+ time too. However, in the time zone that has backward
+ Daylight Saving Time shift, the following may happen: suppose
+ we have a backward DST shift at certain date after 2:59:59,
+ i.e. local time goes 1:59:59, 2:00:00, ... , 2:59:59, (shift
+ here) 2:00:00 (again), ... , 2:59:59 (again), 3:00:00, ... .
+ Now suppose the time has passed the first 2:59:59, has been
+ shifted backward, and now is (the second) 2:20:00. The user
+ does CREATE EVENT with STARTS 'current-date 2:40:00'. Local
+ time 2:40:00 from create statement is treated by time
+ functions as the first such time, so according to UTC it comes
+ before the second 2:20:00. But according to local time it is
+ obviously in the future, so we end up in this branch.
+
+ Since we are in the second pass through 2:00:00--2:59:59, and
+ any local time form this interval is treated by system
+ functions as the time from the first pass, we have to find the
+ time for the next execution that is past the DST-affected
+ interval (past the second 2:59:59 for our example,
+ i.e. starting from 3:00:00). We do this in the loop until the
+ local time is mapped onto future UTC time. 'start' time is in
+ the past, so we may use 'do { } while' here, and add the first
+ interval right away.
+
+ Alternatively, it could be that local_now >= local_start. Now
+ for the example above imagine we do CREATE EVENT with STARTS
+ 'current-date 2:10:00'. Local start 2:10 is in the past (now
+ is local 2:20), so we add an interval, and get next execution
+ time, say, 2:40. It is in the future according to local time,
+ but, again, since we are in the second pass through
+ 2:00:00--2:59:59, 2:40 will be converted into UTC time in the
+ past. So we will end up in this branch again, and may add
+ intervals in a 'do { } while' loop.
+
+ Note that for any given event we may end up here only if event
+ next execution time will map to the time interval that is
+ passed twice, and only if the server was started during the
+ second pass, or the event is being created during the second
+ pass. After that, we never will get here (unless we again
+ start the server during the second pass). In other words,
+ such a condition is extremely rare.
+ */
+ interval.second= seconds;
+ do
+ {
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_SECOND, interval);
+ if (next_time == 0)
+ goto done;
+ }
+ while (next_time <= time_now);
}
- uint multiplier= (uint) (seconds_diff / seconds);
- /*
- Increase the multiplier is the modulus is not zero to make round up.
- Or if time_now==start then we should not execute the same
- event two times for the same time
- get the next exec if the modulus is not
- */
- DBUG_PRINT("info", ("multiplier: %d", multiplier));
- if (seconds_diff % seconds || (!seconds_diff && last_exec->year) ||
- TIME_to_ulonglong_datetime(time_now) ==
- TIME_to_ulonglong_datetime(last_exec))
- ++multiplier;
- interval.second= seconds * multiplier;
- DBUG_PRINT("info", ("multiplier: %lu interval.second: %lu", (ulong) multiplier,
- (ulong) interval.second));
- tmp= *start;
- if (!(ret= date_add_interval(&tmp, INTERVAL_SECOND, interval)))
- *next= tmp;
}
else
{
- /* PRESUMED is that at least one execution took already place */
- int diff_months= (time_now->year - start->year)*12 +
- (time_now->month - start->month);
- /*
- Note: If diff_months is 0 that means we are in the same month as the
- last execution which is also the first execution.
- */
+ long diff_months= (long) (local_now.year - local_start.year)*12 +
+ (local_now.month - local_start.month);
/*
- First we try with the smaller if not then + 1, because if we try with
- directly with +1 we will be after the current date but it could be that
- we will be 1 month ahead, so 2 steps are necessary.
+ Unlike for seconds above, the formula below returns the interval
+ that, when added to the local_start, will give the time in the
+ past, or somewhere in the current month. We are interested in
+ the latter case, to see if this time has already passed, or is
+ yet to come this month.
+
+ Note that the time is guaranteed to be in the past unless
+ (diff_months % months == 0), but no good optimization is
+ possible here, because (diff_months % months == 0) is what will
+ happen most of the time, as get_next_time() will be called right
+ after the execution of the event. We could pass last_executed
+ time to this function, and see if the execution has already
+ happened this month, but for that we will have to convert
+ last_executed from seconds since epoch to local broken-down
+ time, and this will greatly reduce the effect of the
+ optimization. So instead we keep the code simple and clean.
*/
- interval.month= (ulong) ((diff_months / months)*months);
- /*
- Check if the same month as last_exec (always set - prerequisite)
- An event happens at most once per month so there is no way to
- schedule it two times for the current month. This saves us from two
- calls to date_add_interval() if the event was just executed. But if
- the scheduler is started and there was at least 1 scheduled date
- skipped this one does not help and two calls to date_add_interval()
- will be done, which is a bit more expensive but compared to the
- rareness of the case is neglectable.
- */
- if (time_now->year == last_exec->year &&
- time_now->month == last_exec->month)
- interval.month+= (ulong) months;
-
- tmp= *start;
- if ((ret= date_add_interval(&tmp, INTERVAL_MONTH, interval)))
+ interval.month= diff_months - diff_months % months;
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_MONTH, interval);
+ if (next_time == 0)
goto done;
- /* If `tmp` is still before time_now just add one more time the interval */
- if (my_time_compare(&tmp, time_now) == -1)
- {
- interval.month+= (ulong) months;
- tmp= *start;
- if ((ret= date_add_interval(&tmp, INTERVAL_MONTH, interval)))
+ if (next_time <= time_now)
+ {
+ interval.month= months;
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_MONTH, interval);
+ if (next_time == 0)
goto done;
}
- *next= tmp;
- /* assert on that the next is after now */
- DBUG_ASSERT(1==my_time_compare(next, time_now));
}
+ DBUG_ASSERT(time_now < next_time);
+
+ *next= next_time;
+
done:
- DBUG_PRINT("info", ("next: %lu", (long) TIME_to_ulonglong_datetime(next)));
- DBUG_RETURN(ret);
+ DBUG_PRINT("info", ("next_time: %ld", (long) next_time));
+ DBUG_RETURN(next_time == 0);
}
@@ -1227,20 +1330,17 @@ done:
TRUE Error
NOTES
- The time is set in execute_at, if no more executions the latter is set to
- 0000-00-00.
+ The time is set in execute_at, if no more executions the latter is
+ set to 0.
*/
bool
Event_queue_element::compute_next_execution_time()
{
- TIME time_now;
- int tmp;
+ my_time_t time_now;
DBUG_ENTER("Event_queue_element::compute_next_execution_time");
DBUG_PRINT("enter", ("starts: %lu ends: %lu last_executed: %lu this: 0x%lx",
- (long) TIME_to_ulonglong_datetime(&starts),
- (long) TIME_to_ulonglong_datetime(&ends),
- (long) TIME_to_ulonglong_datetime(&last_executed),
+ (long) starts, (long) ends, (long) last_executed,
(long) this));
if (status == Event_queue_element::DISABLED)
@@ -1253,7 +1353,7 @@ Event_queue_element::compute_next_execution_time()
if (!expression)
{
/* Let's check whether it was executed */
- if (last_executed.year)
+ if (last_executed)
{
DBUG_PRINT("info",("One-time event %s.%s of was already executed",
dbname.str, name.str));
@@ -1266,17 +1366,16 @@ Event_queue_element::compute_next_execution_time()
goto ret;
}
- my_tz_UTC->gmt_sec_to_TIME(&time_now, current_thd->query_start());
+ time_now= current_thd->query_start();
- DBUG_PRINT("info",("NOW: [%lu]",
- (ulong) TIME_to_ulonglong_datetime(&time_now)));
+ DBUG_PRINT("info",("NOW: [%lu]", (ulong) time_now));
/* if time_now is after ends don't execute anymore */
- if (!ends_null && (tmp= my_time_compare(&ends, &time_now)) == -1)
+ if (!ends_null && ends < time_now)
{
DBUG_PRINT("info", ("NOW after ENDS, don't execute anymore"));
/* time_now is after ends. don't execute anymore */
- set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ execute_at= 0;
execute_at_null= TRUE;
if (on_completion == Event_queue_element::ON_COMPLETION_DROP)
dropped= TRUE;
@@ -1293,12 +1392,11 @@ Event_queue_element::compute_next_execution_time()
Let's check whether time_now is before starts.
If so schedule for starts.
*/
- if (!starts_null && (tmp= my_time_compare(&time_now, &starts)) < 1)
+ if (!starts_null && time_now <= starts)
{
- if (tmp == 0 && my_time_compare(&starts, &last_executed) == 0)
+ if (time_now == starts && starts == last_executed)
{
/*
- time_now = starts = last_executed
do nothing or we will schedule for second time execution at starts.
*/
}
@@ -1324,26 +1422,25 @@ Event_queue_element::compute_next_execution_time()
If not set then schedule for now.
*/
DBUG_PRINT("info", ("Both STARTS & ENDS are set"));
- if (!last_executed.year)
+ if (!last_executed)
{
DBUG_PRINT("info", ("Not executed so far."));
}
{
- TIME next_exec;
+ my_time_t next_exec;
- if (get_next_time(&next_exec, &starts, &time_now,
- last_executed.year? &last_executed:&starts,
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
(int) expression, interval))
goto err;
/* There was previous execution */
- if (my_time_compare(&ends, &next_exec) == -1)
+ if (ends < next_exec)
{
DBUG_PRINT("info", ("Next execution of %s after ENDS. Stop executing.",
name.str));
/* Next execution after ends. No more executions */
- set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ execute_at= 0;
execute_at_null= TRUE;
if (on_completion == Event_queue_element::ON_COMPLETION_DROP)
dropped= TRUE;
@@ -1352,8 +1449,7 @@ Event_queue_element::compute_next_execution_time()
}
else
{
- DBUG_PRINT("info",("Next[%lu]",
- (ulong) TIME_to_ulonglong_datetime(&next_exec)));
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
execute_at= next_exec;
execute_at_null= FALSE;
}
@@ -1368,15 +1464,14 @@ Event_queue_element::compute_next_execution_time()
Both starts and m_ends are not set, so we schedule for the next
based on last_executed.
*/
- if (last_executed.year)
+ if (last_executed)
{
- TIME next_exec;
- if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
+ my_time_t next_exec;
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
(int) expression, interval))
goto err;
execute_at= next_exec;
- DBUG_PRINT("info",("Next[%lu]",
- (ulong) TIME_to_ulonglong_datetime(&next_exec)));
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
}
else
{
@@ -1398,20 +1493,18 @@ Event_queue_element::compute_next_execution_time()
Hence schedule for starts + m_expression in case last_executed
is not set, otherwise to last_executed + m_expression
*/
- if (!last_executed.year)
+ if (!last_executed)
{
DBUG_PRINT("info", ("Not executed so far."));
}
{
- TIME next_exec;
- if (get_next_time(&next_exec, &starts, &time_now,
- last_executed.year? &last_executed:&starts,
+ my_time_t next_exec;
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
(int) expression, interval))
goto err;
execute_at= next_exec;
- DBUG_PRINT("info",("Next[%lu]",
- (ulong) TIME_to_ulonglong_datetime(&next_exec)));
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
}
execute_at_null= FALSE;
}
@@ -1426,20 +1519,20 @@ Event_queue_element::compute_next_execution_time()
If last_executed is not set then schedule for now
*/
- if (!last_executed.year)
+ if (!last_executed)
execute_at= time_now;
else
{
- TIME next_exec;
+ my_time_t next_exec;
- if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
(int) expression, interval))
goto err;
- if (my_time_compare(&ends, &next_exec) == -1)
+ if (ends < next_exec)
{
DBUG_PRINT("info", ("Next execution after ENDS. Stop executing."));
- set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ execute_at= 0;
execute_at_null= TRUE;
status= Event_queue_element::DISABLED;
status_changed= TRUE;
@@ -1448,8 +1541,7 @@ Event_queue_element::compute_next_execution_time()
}
else
{
- DBUG_PRINT("info", ("Next[%lu]",
- (ulong) TIME_to_ulonglong_datetime(&next_exec)));
+ DBUG_PRINT("info", ("Next[%lu]", (ulong) next_exec));
execute_at= next_exec;
execute_at_null= FALSE;
}
@@ -1458,8 +1550,7 @@ Event_queue_element::compute_next_execution_time()
goto ret;
}
ret:
- DBUG_PRINT("info", ("ret: 0 execute_at: %lu",
- (long) TIME_to_ulonglong_datetime(&execute_at)));
+ DBUG_PRINT("info", ("ret: 0 execute_at: %lu", (long) execute_at));
DBUG_RETURN(FALSE);
err:
DBUG_PRINT("info", ("ret=1"));
@@ -1479,12 +1570,9 @@ err:
void
Event_queue_element::mark_last_executed(THD *thd)
{
- TIME time_now;
-
thd->end_time();
- my_tz_UTC->gmt_sec_to_TIME(&time_now, (my_time_t) thd->query_start());
- last_executed= time_now; /* was execute_at */
+ last_executed= thd->query_start();
last_executed_changed= TRUE;
execution_count++;
@@ -1538,8 +1626,11 @@ Event_queue_element::update_timing_fields(THD *thd)
if (last_executed_changed)
{
+ TIME time;
+ my_tz_UTC->gmt_sec_to_TIME(&time, last_executed);
+
fields[ET_FIELD_LAST_EXECUTED]->set_notnull();
- fields[ET_FIELD_LAST_EXECUTED]->store_time(&last_executed,
+ fields[ET_FIELD_LAST_EXECUTED]->store_time(&time,
MYSQL_TIMESTAMP_DATETIME);
last_executed_changed= FALSE;
}
@@ -1561,6 +1652,26 @@ done:
}
+static
+void
+append_datetime(String *buf, Time_zone *time_zone, my_time_t secs,
+ const char *name, uint len)
+{
+ char dtime_buff[20*2+32];/* +32 to make my_snprintf_{8bit|ucs2} happy */
+ buf->append(STRING_WITH_LEN(" "));
+ buf->append(name, len);
+ buf->append(STRING_WITH_LEN(" '"));
+ /*
+ Pass the buffer and the second param tells fills the buffer and
+ returns the number of chars to copy.
+ */
+ TIME time;
+ time_zone->gmt_sec_to_TIME(&time, secs);
+ buf->append(dtime_buff, my_datetime_to_str(&time, dtime_buff));
+ buf->append(STRING_WITH_LEN("'"));
+}
+
+
/*
Get SHOW CREATE EVENT as string
@@ -1600,17 +1711,17 @@ Event_timed::get_create_event(THD *thd, String *buf)
buf->append(' ');
LEX_STRING *ival= &interval_type_to_name[interval];
buf->append(ival->str, ival->length);
+
+ if (!starts_null)
+ append_datetime(buf, time_zone, starts, STRING_WITH_LEN("STARTS"));
+
+ if (!ends_null)
+ append_datetime(buf, time_zone, ends, STRING_WITH_LEN("ENDS"));
}
else
{
- char dtime_buff[20*2+32];/* +32 to make my_snprintf_{8bit|ucs2} happy */
- buf->append(STRING_WITH_LEN(" ON SCHEDULE AT '"));
- /*
- Pass the buffer and the second param tells fills the buffer and
- returns the number of chars to copy.
- */
- buf->append(dtime_buff, my_datetime_to_str(&execute_at, dtime_buff));
- buf->append(STRING_WITH_LEN("'"));
+ append_datetime(buf, time_zone, execute_at,
+ STRING_WITH_LEN("ON SCHEDULE AT"));
}
if (on_completion == Event_timed::ON_COMPLETION_DROP)
@@ -1654,6 +1765,7 @@ int
Event_job_data::get_fake_create_event(THD *thd, String *buf)
{
DBUG_ENTER("Event_job_data::get_create_event");
+ /* FIXME: "EVERY 3337 HOUR" is asking for trouble. */
buf->append(STRING_WITH_LEN("CREATE EVENT anonymous ON SCHEDULE "
"EVERY 3337 HOUR DO "));
buf->append(body.str, body.length);
@@ -1705,6 +1817,9 @@ Event_job_data::execute(THD *thd)
sphead->m_flags|= sp_head::LOG_SLOW_STATEMENTS;
sphead->m_flags|= sp_head::LOG_GENERAL_LOG;
+ /* Execute the event in its time zone. */
+ thd->variables.time_zone= time_zone;
+
ret= sphead->execute_procedure(thd, &empty_item_list);
}
else
diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h
index 4346b0eb5b8..8e013e40400 100644
--- a/sql/event_data_objects.h
+++ b/sql/event_data_objects.h
@@ -58,15 +58,20 @@ public:
LEX_STRING name;
LEX_STRING definer;// combination of user and host
+ Time_zone *time_zone;
+
Event_basic();
virtual ~Event_basic();
virtual int
- load_from_row(TABLE *table) = 0;
+ load_from_row(THD *thd, TABLE *table) = 0;
protected:
bool
load_string_fields(Field **fields, ...);
+
+ bool
+ load_time_zone(THD *thd, const LEX_STRING tz_name);
};
@@ -92,11 +97,11 @@ public:
enum enum_on_completion on_completion;
enum enum_status status;
- TIME last_executed;
- TIME execute_at;
- TIME starts;
- TIME ends;
+ my_time_t last_executed;
+ my_time_t execute_at;
+ my_time_t starts;
+ my_time_t ends;
my_bool starts_null;
my_bool ends_null;
my_bool execute_at_null;
@@ -112,7 +117,7 @@ public:
virtual ~Event_queue_element();
virtual int
- load_from_row(TABLE *table);
+ load_from_row(THD *thd, TABLE *table);
bool
compute_next_execution_time();
@@ -168,7 +173,7 @@ public:
init();
virtual int
- load_from_row(TABLE *table);
+ load_from_row(THD *thd, TABLE *table);
int
get_create_event(THD *thd, String *buf);
@@ -192,7 +197,7 @@ public:
virtual ~Event_job_data();
virtual int
- load_from_row(TABLE *table);
+ load_from_row(THD *thd, TABLE *table);
int
execute(THD *thd);
@@ -224,6 +229,11 @@ public:
};
enum enum_on_completion on_completion;
enum enum_status status;
+ /*
+ do_not_create will be set if STARTS time is in the past and
+ on_completion == ON_COMPLETION_DROP.
+ */
+ bool do_not_create;
const uchar *body_begin;
@@ -237,9 +247,9 @@ public:
Item* item_ends;
Item* item_execute_at;
- TIME starts;
- TIME ends;
- TIME execute_at;
+ my_time_t starts;
+ my_time_t ends;
+ my_time_t execute_at;
my_bool starts_null;
my_bool ends_null;
my_bool execute_at_null;
@@ -284,6 +294,9 @@ private:
void
report_bad_value(const char *item_name, Item *bad_item);
+ void
+ check_if_in_the_past(THD *thd, my_time_t ltime_utc);
+
Event_parse_data(const Event_parse_data &); /* Prevent use of these */
void operator=(Event_parse_data &);
};
diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc
index 940930ec4c6..6be7411af7b 100644
--- a/sql/event_db_repository.cc
+++ b/sql/event_db_repository.cc
@@ -118,6 +118,11 @@ const TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] =
{ C_STRING_WITH_LEN("comment") },
{ C_STRING_WITH_LEN("char(64)") },
{ C_STRING_WITH_LEN("utf8") }
+ },
+ {
+ { C_STRING_WITH_LEN("time_zone") },
+ { C_STRING_WITH_LEN("char(64)") },
+ { C_STRING_WITH_LEN("latin1") }
}
};
@@ -183,6 +188,14 @@ mysql_event_fill_row(THD *thd, TABLE *table, Event_parse_data *et,
if (et->expression)
{
+ const String *tz_name= thd->variables.time_zone->get_name();
+ if (!is_update || !et->starts_null)
+ {
+ fields[ET_FIELD_TIME_ZONE]->set_notnull();
+ fields[ET_FIELD_TIME_ZONE]->store(tz_name->ptr(), tz_name->length(),
+ tz_name->charset());
+ }
+
fields[ET_FIELD_INTERVAL_EXPR]->set_notnull();
fields[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, TRUE);
@@ -197,26 +210,40 @@ mysql_event_fill_row(THD *thd, TABLE *table, Event_parse_data *et,
if (!et->starts_null)
{
+ TIME time;
+ my_tz_UTC->gmt_sec_to_TIME(&time, et->starts);
+
fields[ET_FIELD_STARTS]->set_notnull();
- fields[ET_FIELD_STARTS]->store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME);
+ fields[ET_FIELD_STARTS]->store_time(&time, MYSQL_TIMESTAMP_DATETIME);
}
if (!et->ends_null)
{
+ TIME time;
+ my_tz_UTC->gmt_sec_to_TIME(&time, et->ends);
+
fields[ET_FIELD_ENDS]->set_notnull();
- fields[ET_FIELD_ENDS]->store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME);
+ fields[ET_FIELD_ENDS]->store_time(&time, MYSQL_TIMESTAMP_DATETIME);
}
}
- else if (et->execute_at.year)
+ else if (et->execute_at)
{
+ const String *tz_name= thd->variables.time_zone->get_name();
+ fields[ET_FIELD_TIME_ZONE]->set_notnull();
+ fields[ET_FIELD_TIME_ZONE]->store(tz_name->ptr(), tz_name->length(),
+ tz_name->charset());
+
fields[ET_FIELD_INTERVAL_EXPR]->set_null();
fields[ET_FIELD_TRANSIENT_INTERVAL]->set_null();
fields[ET_FIELD_STARTS]->set_null();
fields[ET_FIELD_ENDS]->set_null();
+ TIME time;
+ my_tz_UTC->gmt_sec_to_TIME(&time, et->execute_at);
+
fields[ET_FIELD_EXECUTE_AT]->set_notnull();
fields[ET_FIELD_EXECUTE_AT]->
- store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME);
+ store_time(&time, MYSQL_TIMESTAMP_DATETIME);
}
else
{
@@ -527,6 +554,8 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data,
if (check_parse_params(thd, parse_data))
goto err;
+ if (parse_data->do_not_create)
+ goto ok;
DBUG_PRINT("info", ("open mysql.event for update"));
if (open_event_table(thd, TL_WRITE, &table))
@@ -587,7 +616,7 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data,
goto err;
}
- if (!(parse_data->expression) && !(parse_data->execute_at.year))
+ if (!(parse_data->expression) && !(parse_data->execute_at))
{
DBUG_PRINT("error", ("neither expression nor execute_at are set!"));
my_error(ER_EVENT_NEITHER_M_EXPR_NOR_M_AT, MYF(0));
@@ -664,7 +693,7 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data,
goto err;
}
- if (check_parse_params(thd, parse_data))
+ if (check_parse_params(thd, parse_data) || parse_data->do_not_create)
goto err;
DBUG_PRINT("info", ("dbname: %s", parse_data->dbname.str));
@@ -964,7 +993,7 @@ Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname,
my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
else if ((ret= find_named_event(thd, dbname, name, table)))
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str);
- else if ((ret= etn->load_from_row(table)))
+ else if ((ret= etn->load_from_row(thd, table)))
my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event");
if (table)
diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h
index 1457fb64e2e..4991e2d0aa2 100644
--- a/sql/event_db_repository.h
+++ b/sql/event_db_repository.h
@@ -35,6 +35,7 @@ enum enum_events_table_field
ET_FIELD_ON_COMPLETION,
ET_FIELD_SQL_MODE,
ET_FIELD_COMMENT,
+ ET_FIELD_TIME_ZONE,
ET_FIELD_COUNT /* a cool trick to count the number of fields :) */
};
diff --git a/sql/event_queue.cc b/sql/event_queue.cc
index 296c30506f6..bcfe0a222f1 100644
--- a/sql/event_queue.cc
+++ b/sql/event_queue.cc
@@ -65,8 +65,13 @@ struct event_queue_param
static int
event_queue_element_compare_q(void *vptr, byte* a, byte *b)
{
- return my_time_compare(&((Event_queue_element *)a)->execute_at,
- &((Event_queue_element *)b)->execute_at);
+ /*
+ Note that no overflow is possible here because both values are
+ non-negative, and subtraction is done in the signed my_time_t
+ type.
+ */
+ return (((Event_queue_element *)a)->execute_at
+ - ((Event_queue_element *)b)->execute_at);
}
@@ -84,7 +89,7 @@ Event_queue::Event_queue()
{
mutex_last_unlocked_in_func= mutex_last_locked_in_func=
mutex_last_attempted_lock_in_func= "";
- set_zero_time(&next_activation_at, MYSQL_TIMESTAMP_DATETIME);
+ next_activation_at= 0;
}
@@ -504,15 +509,11 @@ Event_queue::dbug_dump_queue(time_t now)
DBUG_PRINT("info", ("exec_at: %lu starts: %lu ends: %lu execs_so_far: %u "
"expr: %ld et.exec_at: %ld now: %ld "
"(et.exec_at - now): %d if: %d",
- (long) TIME_to_ulonglong_datetime(&et->execute_at),
- (long) TIME_to_ulonglong_datetime(&et->starts),
- (long) TIME_to_ulonglong_datetime(&et->ends),
- et->execution_count,
- (long) et->expression,
- (long) (sec_since_epoch_TIME(&et->execute_at)),
- (long) now,
- (int) (sec_since_epoch_TIME(&et->execute_at) - now),
- sec_since_epoch_TIME(&et->execute_at) <= now));
+ (long) et->execute_at, (long) et->starts,
+ (long) et->ends, et->execution_count,
+ (long) et->expression, (long) et->execute_at,
+ (long) now, (int) (et->execute_at - now),
+ et->execute_at <= now));
}
DBUG_VOID_RETURN;
#endif
@@ -541,7 +542,6 @@ Event_queue::get_top_for_execution_if_time(THD *thd,
Event_queue_element_for_exec **event_name)
{
bool ret= FALSE;
- struct timespec top_time;
*event_name= NULL;
DBUG_ENTER("Event_queue::get_top_for_execution_if_time");
@@ -560,7 +560,7 @@ Event_queue::get_top_for_execution_if_time(THD *thd,
if (!queue.elements)
{
/* There are no events in the queue */
- set_zero_time(&next_activation_at, MYSQL_TIMESTAMP_DATETIME);
+ next_activation_at= 0;
/* Wait on condition until signaled. Release LOCK_queue while waiting. */
cond_wait(thd, NULL, queue_empty_msg, SCHED_FUNC, __LINE__);
@@ -572,16 +572,16 @@ Event_queue::get_top_for_execution_if_time(THD *thd,
thd->end_time(); /* Get current time */
- time_t seconds_to_next_event=
- sec_since_epoch_TIME(&top->execute_at) - thd->query_start();
next_activation_at= top->execute_at;
- if (seconds_to_next_event > 0)
+ if (next_activation_at > thd->query_start())
{
/*
Not yet time for top event, wait on condition with
time or until signaled. Release LOCK_queue while waiting.
*/
- set_timespec(top_time, seconds_to_next_event);
+ struct timespec top_time;
+ top_time.tv_sec= next_activation_at;
+ top_time.tv_nsec= 0;
cond_wait(thd, &top_time, queue_wait_msg, SCHED_FUNC, __LINE__);
continue;
@@ -759,10 +759,11 @@ Event_queue::dump_internal_status()
printf("Last lock attempt at: %s:%u\n", mutex_last_attempted_lock_in_func,
mutex_last_attempted_lock_at_line);
printf("WOC : %s\n", waiting_on_cond? "YES":"NO");
+
+ TIME time;
+ my_tz_UTC->gmt_sec_to_TIME(&time, next_activation_at);
printf("Next activation : %04d-%02d-%02d %02d:%02d:%02d\n",
- next_activation_at.year, next_activation_at.month,
- next_activation_at.day, next_activation_at.hour,
- next_activation_at.minute, next_activation_at.second);
+ time.year, time.month, time.day, time.hour, time.minute, time.second);
DBUG_VOID_RETURN;
}
diff --git a/sql/event_queue.h b/sql/event_queue.h
index a1237e1b52c..338a6c8f903 100644
--- a/sql/event_queue.h
+++ b/sql/event_queue.h
@@ -86,7 +86,7 @@ protected:
/* The sorted queue with the Event_queue_element objects */
QUEUE queue;
- TIME next_activation_at;
+ my_time_t next_activation_at;
uint mutex_last_locked_at_line;
uint mutex_last_unlocked_at_line;
diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc
index 64bba756be9..3c8ecf6ca3a 100644
--- a/sql/event_scheduler.cc
+++ b/sql/event_scheduler.cc
@@ -574,6 +574,14 @@ Event_scheduler::execute_top(THD *thd, Event_queue_element_for_exec *event_name)
DBUG_PRINT("info", ("Event %s@%s ready for start",
event_name->dbname.str, event_name->name.str));
+ /*
+ TODO: should use thread pool here, preferably with an upper limit
+ on number of threads: if too many events are scheduled for the
+ same time, starting all of them at once won't help them run truly
+ in parallel (because of the great amount of synchronization), so
+ we may as well execute them in sequence, keeping concurrency at a
+ reasonable level.
+ */
/* Major failure */
if ((res= pthread_create(&th, &connection_attrib, event_worker_thread,
event_name)))
diff --git a/sql/events.cc b/sql/events.cc
index f73dc97e7c2..88014ee34af 100644
--- a/sql/events.cc
+++ b/sql/events.cc
@@ -326,7 +326,8 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists)
pthread_mutex_lock(&LOCK_event_metadata);
/* On error conditions my_error() is called so no need to handle here */
- if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists)))
+ if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists)) &&
+ !parse_data->do_not_create)
{
Event_queue_element *new_element;
@@ -527,6 +528,10 @@ Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name)
field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len));
+ const String *tz_name= et->time_zone->get_name();
+ field_list.push_back(new Item_empty_string("time_zone",
+ tz_name->length()));
+
field_list.push_back(new Item_empty_string("Create Event",
show_str.length()));
@@ -539,6 +544,8 @@ Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name)
protocol->store((char*) sql_mode_str, sql_mode_len, scs);
+ protocol->store((char*) tz_name->ptr(), tz_name->length(), scs);
+
protocol->store(show_str.c_ptr(), show_str.length(), scs);
ret= protocol->write();
send_eof(thd);
@@ -942,7 +949,7 @@ Events::load_events_from_db(THD *thd)
}
DBUG_PRINT("info", ("Loading event from row."));
- if ((ret= et->load_from_row(table)))
+ if ((ret= et->load_from_row(thd, table)))
{
sql_print_error("SCHEDULER: Error while loading from mysql.event. "
"Table probably corrupted");
@@ -967,7 +974,7 @@ Events::load_events_from_db(THD *thd)
Event_job_data temp_job_data;
DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str));
- temp_job_data.load_from_row(table);
+ temp_job_data.load_from_row(thd, table);
/*
We load only on scheduler root just to check whether the body
diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt
index c5bda4ac7a6..f7ffad697b0 100644
--- a/sql/share/errmsg.txt
+++ b/sql/share/errmsg.txt
@@ -5873,8 +5873,7 @@ ER_EVENT_ENDS_BEFORE_STARTS
eng "ENDS is either invalid or before STARTS"
ger "ENDS ist entweder ungültig oder liegt vor STARTS"
ER_EVENT_EXEC_TIME_IN_THE_PAST
- eng "Activation (AT) time is in the past"
- ger "Aktivierungszeit (AT) liegt in der Vergangenheit"
+ eng "Event execution time is in the past. Event has been disabled"
ER_EVENT_OPEN_TABLE_FAILED
eng "Failed to open mysql.event"
ger "Öffnen von mysql.event fehlgeschlagen"
@@ -6052,3 +6051,7 @@ ER_DUP_ENTRY_WITH_KEY_NAME 23000 S1009
ER_BINLOG_PURGE_EMFILE
eng "Too many files opened, please execute the command again"
ger "Zu viele offene Dateien, bitte führen Sie den Befehl noch einmal aus"
+ER_EVENT_CANNOT_CREATE_IN_THE_PAST
+ eng "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. Event has not been created"
+ER_EVENT_CANNOT_ALTER_IN_THE_PAST
+ eng "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. Event has not been altered"
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index bdfb7512979..0c56abbdfc8 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -38,6 +38,7 @@ enum enum_i_s_events_fields
ISE_EVENT_SCHEMA,
ISE_EVENT_NAME,
ISE_DEFINER,
+ ISE_TIME_ZONE,
ISE_EVENT_BODY,
ISE_EVENT_DEFINITION,
ISE_EVENT_TYPE,
@@ -4310,7 +4311,7 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
restore_record(sch_table, s->default_values);
- if (et.load_from_row(event_table))
+ if (et.load_from_row(thd, event_table))
{
my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0));
DBUG_RETURN(1);
@@ -4337,6 +4338,9 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
store(et.name.str, et.name.length, scs);
sch_table->field[ISE_DEFINER]->
store(et.definer.str, et.definer.length, scs);
+ const String *tz_name= et.time_zone->get_name();
+ sch_table->field[ISE_TIME_ZONE]->
+ store(tz_name->ptr(), tz_name->length(), scs);
sch_table->field[ISE_EVENT_BODY]->
store(STRING_WITH_LEN("SQL"), scs);
sch_table->field[ISE_EVENT_DEFINITION]->
@@ -4353,6 +4357,8 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
store((const char*)sql_mode_str, sql_mode_len, scs);
}
+ int not_used=0;
+
if (et.expression)
{
String show_str;
@@ -4372,15 +4378,17 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
sch_table->field[ISE_INTERVAL_FIELD]->store(ival->str, ival->length, scs);
/* starts & ends . STARTS is always set - see sql_yacc.yy */
+ et.time_zone->gmt_sec_to_TIME(&time, et.starts);
sch_table->field[ISE_STARTS]->set_notnull();
sch_table->field[ISE_STARTS]->
- store_time(&et.starts, MYSQL_TIMESTAMP_DATETIME);
+ store_time(&time, MYSQL_TIMESTAMP_DATETIME);
if (!et.ends_null)
{
+ et.time_zone->gmt_sec_to_TIME(&time, et.ends);
sch_table->field[ISE_ENDS]->set_notnull();
sch_table->field[ISE_ENDS]->
- store_time(&et.ends, MYSQL_TIMESTAMP_DATETIME);
+ store_time(&time, MYSQL_TIMESTAMP_DATETIME);
}
}
else
@@ -4388,9 +4396,10 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
/* type */
sch_table->field[ISE_EVENT_TYPE]->store(STRING_WITH_LEN("ONE TIME"), scs);
+ et.time_zone->gmt_sec_to_TIME(&time, et.execute_at);
sch_table->field[ISE_EXECUTE_AT]->set_notnull();
sch_table->field[ISE_EXECUTE_AT]->
- store_time(&et.execute_at, MYSQL_TIMESTAMP_DATETIME);
+ store_time(&time, MYSQL_TIMESTAMP_DATETIME);
}
/* status */
@@ -4407,7 +4416,6 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
sch_table->field[ISE_ON_COMPLETION]->
store(STRING_WITH_LEN("PRESERVE"), scs);
- int not_used=0;
number_to_datetime(et.created, &time, 0, &not_used);
DBUG_ASSERT(not_used==0);
sch_table->field[ISE_CREATED]->store_time(&time, MYSQL_TIMESTAMP_DATETIME);
@@ -4417,11 +4425,12 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
sch_table->field[ISE_LAST_ALTERED]->
store_time(&time, MYSQL_TIMESTAMP_DATETIME);
- if (et.last_executed.year)
+ if (et.last_executed)
{
+ et.time_zone->gmt_sec_to_TIME(&time, et.last_executed);
sch_table->field[ISE_LAST_EXECUTED]->set_notnull();
sch_table->field[ISE_LAST_EXECUTED]->
- store_time(&et.last_executed, MYSQL_TIMESTAMP_DATETIME);
+ store_time(&time, MYSQL_TIMESTAMP_DATETIME);
}
sch_table->field[ISE_EVENT_COMMENT]->
@@ -5427,6 +5436,7 @@ ST_FIELD_INFO events_fields_info[]=
{"EVENT_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Db"},
{"EVENT_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Name"},
{"DEFINER", 77, MYSQL_TYPE_STRING, 0, 0, "Definer"},
+ {"TIME_ZONE", 64, MYSQL_TYPE_STRING, 0, 0, "Time zone"},
{"EVENT_BODY", 8, MYSQL_TYPE_STRING, 0, 0, 0},
{"EVENT_DEFINITION", 65535, MYSQL_TYPE_STRING, 0, 0, 0},
{"EVENT_TYPE", 9, MYSQL_TYPE_STRING, 0, 0, "Type"},