Commit 36cfa4df authored by andrey@lmy004's avatar andrey@lmy004

fix for bug #17494 (The algorithm for calculating execution times is not fully correct)

This also should fix 17493 and 17346, and probably 16397 (not tested).
WL#1034 (Internal CRON)
(post-review commit)
parent 3c1f4e3c
...@@ -106,7 +106,6 @@ drop event if exists event3; ...@@ -106,7 +106,6 @@ drop event if exists event3;
Warnings: Warnings:
Note 1305 Event event3 does not exist Note 1305 Event event3 does not exist
create event event3 on schedule every 50 + 10 minute starts date_add("20100101", interval 5 minute) ends date_add("20151010", interval 5 day) comment "portokala_comment" DO insert into t_event3 values (unix_timestamp(), rand()); create event event3 on schedule every 50 + 10 minute starts date_add("20100101", 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 count(*) from t_event3; select count(*) from t_event3;
count(*) count(*)
0 0
...@@ -232,6 +231,9 @@ Db Name Definer Type Execute at Interval value Interval field Starts Ends Status ...@@ -232,6 +231,9 @@ Db Name Definer Type Execute at Interval value Interval field Starts Ends Status
events_test intact_check root@localhost RECURRING NULL 10 HOUR # # ENABLED events_test intact_check root@localhost RECURRING NULL 10 HOUR # # ENABLED
CREATE TABLE event_like LIKE mysql.event; CREATE TABLE event_like LIKE mysql.event;
INSERT INTO event_like SELECT * FROM mysql.event; INSERT INTO event_like SELECT * FROM mysql.event;
ALTER TABLE mysql.event MODIFY db char(64) character set cp1251 default '';
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
ERROR HY000: Cannot load from mysql.event. Table probably corrupted. See error log.
ALTER TABLE mysql.event MODIFY db char(20) character set utf8 collate utf8_bin default ''; ALTER TABLE mysql.event MODIFY db char(20) character set utf8 collate utf8_bin default '';
SHOW CREATE TABLE mysql.event; SHOW CREATE TABLE mysql.event;
Table Create Table Table Create Table
...@@ -260,11 +262,10 @@ ALTER TABLE mysql.event MODIFY db char(64) character set utf8 collate utf8_bin d ...@@ -260,11 +262,10 @@ ALTER TABLE mysql.event MODIFY db char(64) character set utf8 collate utf8_bin d
"This should work" "This should work"
SHOW EVENTS; SHOW EVENTS;
Db Name Definer Type Execute at Interval value Interval field Starts Ends Status Db Name Definer Type Execute at Interval value Interval field Starts Ends Status
events_test intact_check root@localhost RECURRING NULL 10 HOUR # # ENABLED events_test intact_check root@localhost RECURRING NULL 10 HOUR # # ENABLED
ALTER TABLE mysql.event MODIFY db char(64) character set cp1251 default '';
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
ERROR HY000: Cannot load from mysql.event. Table probably corrupted. See error log.
ALTER TABLE mysql.event MODIFY db varchar(64) character set utf8 collate utf8_bin default ''; ALTER TABLE mysql.event MODIFY db varchar(64) character set utf8 collate utf8_bin default '';
Warnings:
Warning 1265 Data truncated for column 'db' at row 1
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS; SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
ERROR HY000: Cannot load from mysql.event. Table probably corrupted. See error log. ERROR HY000: Cannot load from mysql.event. Table probably corrupted. See error log.
ALTER TABLE mysql.event DROP comment, DROP starts; ALTER TABLE mysql.event DROP comment, DROP starts;
......
CREATE DATABASE IF NOT EXISTS events_test;
USE events_test;
CREATE TABLE table_1(a int);
CREATE TABLE table_2(a int);
CREATE TABLE table_3(a int);
CREATE TABLE table_4(a int);
SET GLOBAL event_scheduler=1;
CREATE EVENT two_sec ON SCHEDULE EVERY 2 SECOND DO INSERT INTO table_1 VALUES(1);
CREATE EVENT start_n_end
ON SCHEDULE EVERY 1 SECOND
ENDS NOW() + INTERVAL 6 SECOND
ON COMPLETION PRESERVE
DO INSERT INTO table_2 VALUES(1);
CREATE EVENT only_one_time ON SCHEDULE EVERY 2 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_3 VALUES(1);
CREATE EVENT two_time ON SCHEDULE EVERY 1 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_4 VALUES(1);
SELECT IF(SUM(a) >= 4, 'OK', 'ERROR') FROM table_1;
IF(SUM(a) >= 4, 'OK', 'ERROR')
OK
SELECT IF(SUM(a) >= 5, 'OK', 'ERROR') FROM table_2;
IF(SUM(a) >= 5, 'OK', 'ERROR')
OK
SELECT IF(SUM(a) > 0, 'OK', 'ERROR') FROM table_3;
IF(SUM(a) > 0, 'OK', 'ERROR')
OK
SELECT IF(SUM(a) > 0, 'OK', 'ERROR') FROM table_4;
IF(SUM(a) > 0, 'OK', 'ERROR')
OK
DROP EVENT two_sec;
SELECT IF(TIME_TO_SEC(TIMEDIFF(ENDS,STARTS))=6, 'OK', 'ERROR') FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA=DATABASE() AND EVENT_NAME='start_n_end' AND ENDS IS NOT NULL;
IF(TIME_TO_SEC(TIMEDIFF(ENDS,STARTS))=6, 'OK', 'ERROR')
OK
SELECT IF(LAST_EXECUTED-ENDS < 2, 'OK', 'ERROR') FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA=DATABASE() AND EVENT_NAME='start_n_end' AND ENDS IS NOT NULL;
IF(LAST_EXECUTED-ENDS < 2, 'OK', 'ERROR')
OK
DROP EVENT start_n_end;
"Already dropped because ended. Therefore an error."
DROP EVENT only_one_time;
ERROR HY000: Unknown event 'only_one_time'
"Already dropped because ended. Therefore an error."
DROP EVENT two_time;
ERROR HY000: Unknown event 'two_time'
DROP TABLE table_1;
DROP TABLE table_2;
DROP TABLE table_3;
DROP TABLE table_4;
DROP DATABASE events_test;
...@@ -101,7 +101,6 @@ set global event_scheduler = 0; ...@@ -101,7 +101,6 @@ set global event_scheduler = 0;
create table t_event3 (a int, b float); create table t_event3 (a int, b float);
drop event if exists event3; drop event if exists event3;
create event event3 on schedule every 50 + 10 minute starts date_add("20100101", interval 5 minute) ends date_add("20151010", interval 5 day) comment "portokala_comment" DO insert into t_event3 values (unix_timestamp(), rand()); create event event3 on schedule every 50 + 10 minute starts date_add("20100101", 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 count(*) from t_event3; select count(*) from t_event3;
drop event event3; drop event event3;
drop table t_event3; drop table t_event3;
...@@ -202,29 +201,25 @@ CREATE TABLE event_like LIKE mysql.event; ...@@ -202,29 +201,25 @@ CREATE TABLE event_like LIKE mysql.event;
INSERT INTO event_like SELECT * FROM mysql.event; INSERT INTO event_like SELECT * FROM mysql.event;
#sleep a bit or we won't catch the change of time #sleep a bit or we won't catch the change of time
--sleep 1 --sleep 1
ALTER TABLE mysql.event MODIFY db char(64) character set cp1251 default '';
--error 1526
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
ALTER TABLE mysql.event MODIFY db char(20) character set utf8 collate utf8_bin default ''; ALTER TABLE mysql.event MODIFY db char(20) character set utf8 collate utf8_bin default '';
#wait a bit or we won't see the difference because of seconds resolution #wait a bit or we won't see the difference because of seconds resolution
--sleep 1
SHOW CREATE TABLE mysql.event; SHOW CREATE TABLE mysql.event;
--error 1526 --error 1526
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS; SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
--sleep 1 --sleep 1
ALTER TABLE mysql.event MODIFY db char(64) character set utf8 collate utf8_bin default ''; ALTER TABLE mysql.event MODIFY db char(64) character set utf8 collate utf8_bin default '';
--sleep 1
--echo "This should work" --echo "This should work"
--replace_column 8 # 9 # --replace_column 8 # 9 #
SHOW EVENTS; SHOW EVENTS;
--sleep 1 --sleep 1
ALTER TABLE mysql.event MODIFY db char(64) character set cp1251 default '';
--error 1526
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
--sleep 1
ALTER TABLE mysql.event MODIFY db varchar(64) character set utf8 collate utf8_bin default ''; ALTER TABLE mysql.event MODIFY db varchar(64) character set utf8 collate utf8_bin default '';
--sleep 2
--error 1526 --error 1526
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS; SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
--sleep 1
ALTER TABLE mysql.event DROP comment, DROP starts; ALTER TABLE mysql.event DROP comment, DROP starts;
--sleep 1
--error 1525 --error 1525
SELECT event_name FROM INFORMATION_SCHEMA.EVENTS; SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
DROP TABLE mysql.event; DROP TABLE mysql.event;
......
CREATE DATABASE IF NOT EXISTS events_test;
USE events_test;
CREATE TABLE table_1(a int);
CREATE TABLE table_2(a int);
CREATE TABLE table_3(a int);
CREATE TABLE table_4(a int);
SET GLOBAL event_scheduler=1;
CREATE EVENT two_sec ON SCHEDULE EVERY 2 SECOND DO INSERT INTO table_1 VALUES(1);
CREATE EVENT start_n_end
ON SCHEDULE EVERY 1 SECOND
ENDS NOW() + INTERVAL 6 SECOND
ON COMPLETION PRESERVE
DO INSERT INTO table_2 VALUES(1);
--sleep 5
CREATE EVENT only_one_time ON SCHEDULE EVERY 2 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_3 VALUES(1);
CREATE EVENT two_time ON SCHEDULE EVERY 1 SECOND ENDS NOW() + INTERVAL 1 SECOND DO INSERT INTO table_4 VALUES(1);
--sleep 5
SELECT IF(SUM(a) >= 4, 'OK', 'ERROR') FROM table_1;
SELECT IF(SUM(a) >= 5, 'OK', 'ERROR') FROM table_2;
SELECT IF(SUM(a) > 0, 'OK', 'ERROR') FROM table_3;
SELECT IF(SUM(a) > 0, 'OK', 'ERROR') FROM table_4;
DROP EVENT two_sec;
SELECT IF(TIME_TO_SEC(TIMEDIFF(ENDS,STARTS))=6, 'OK', 'ERROR') FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA=DATABASE() AND EVENT_NAME='start_n_end' AND ENDS IS NOT NULL;
SELECT IF(LAST_EXECUTED-ENDS < 2, 'OK', 'ERROR') FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA=DATABASE() AND EVENT_NAME='start_n_end' AND ENDS IS NOT NULL;
DROP EVENT start_n_end;
--echo "Already dropped because ended. Therefore an error."
--error 1517
DROP EVENT only_one_time;
--echo "Already dropped because ended. Therefore an error."
--error 1517
DROP EVENT two_time;
DROP TABLE table_1;
DROP TABLE table_2;
DROP TABLE table_3;
DROP TABLE table_4;
DROP DATABASE events_test;
...@@ -1047,13 +1047,6 @@ evex_load_and_compile_event(THD * thd, sp_name *spn, LEX_STRING definer, ...@@ -1047,13 +1047,6 @@ evex_load_and_compile_event(THD * thd, sp_name *spn, LEX_STRING definer,
thd->restore_backup_open_tables_state(&backup); thd->restore_backup_open_tables_state(&backup);
if (ret) if (ret)
goto done; 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(); ett->compute_next_execution_time();
if (use_lock) if (use_lock)
......
...@@ -42,6 +42,8 @@ pthread_mutex_t LOCK_event_arrays, // mutex for when working with t ...@@ -42,6 +42,8 @@ pthread_mutex_t LOCK_event_arrays, // mutex for when working with t
LOCK_workers_count, // mutex for when inc/dec uint workers_count LOCK_workers_count, // mutex for when inc/dec uint workers_count
LOCK_evex_running; // mutes for managing bool evex_is_running LOCK_evex_running; // mutes for managing bool evex_is_running
static pthread_mutex_t LOCK_evex_main_thread; // mutex for when working with the queue
bool scheduler_main_thread_running= false;
bool evex_is_running= false; bool evex_is_running= false;
...@@ -111,6 +113,7 @@ evex_init_mutexes() ...@@ -111,6 +113,7 @@ evex_init_mutexes()
pthread_mutex_init(&LOCK_event_arrays, MY_MUTEX_INIT_FAST); pthread_mutex_init(&LOCK_event_arrays, MY_MUTEX_INIT_FAST);
pthread_mutex_init(&LOCK_workers_count, MY_MUTEX_INIT_FAST); pthread_mutex_init(&LOCK_workers_count, MY_MUTEX_INIT_FAST);
pthread_mutex_init(&LOCK_evex_running, MY_MUTEX_INIT_FAST); pthread_mutex_init(&LOCK_evex_running, MY_MUTEX_INIT_FAST);
pthread_mutex_init(&LOCK_evex_main_thread, MY_MUTEX_INIT_FAST);
event_executor_running_global_var= opt_event_executor; event_executor_running_global_var= opt_event_executor;
} }
...@@ -241,6 +244,7 @@ shutdown_events() ...@@ -241,6 +244,7 @@ shutdown_events()
pthread_mutex_destroy(&LOCK_event_arrays); pthread_mutex_destroy(&LOCK_event_arrays);
pthread_mutex_destroy(&LOCK_workers_count); pthread_mutex_destroy(&LOCK_workers_count);
pthread_mutex_destroy(&LOCK_evex_running); pthread_mutex_destroy(&LOCK_evex_running);
pthread_mutex_destroy(&LOCK_evex_main_thread);
} }
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -351,6 +355,7 @@ executor_wait_till_next_event_exec(THD *thd) ...@@ -351,6 +355,7 @@ executor_wait_till_next_event_exec(THD *thd)
t2sleep= evex_time_diff(&et->execute_at, &time_now); t2sleep= evex_time_diff(&et->execute_at, &time_now);
VOID(pthread_mutex_unlock(&LOCK_event_arrays)); VOID(pthread_mutex_unlock(&LOCK_event_arrays));
t2sleep*=20;
DBUG_PRINT("evex main thread",("unlocked LOCK_event_arrays")); DBUG_PRINT("evex main thread",("unlocked LOCK_event_arrays"));
if (t2sleep > 0) if (t2sleep > 0)
{ {
...@@ -366,7 +371,7 @@ executor_wait_till_next_event_exec(THD *thd) ...@@ -366,7 +371,7 @@ executor_wait_till_next_event_exec(THD *thd)
modified)) modified))
{ {
DBUG_PRINT("evex main thread",("will sleep a bit more.")); DBUG_PRINT("evex main thread",("will sleep a bit more."));
my_sleep(1000000); my_sleep(50000);
} }
DBUG_PRINT("info",("saved_modified=%llu current=%llu", modified, DBUG_PRINT("info",("saved_modified=%llu current=%llu", modified,
evex_queue_num_elements(EVEX_EQ_NAME)? evex_queue_num_elements(EVEX_EQ_NAME)?
...@@ -407,10 +412,23 @@ event_executor_main(void *arg) ...@@ -407,10 +412,23 @@ event_executor_main(void *arg)
THD *thd; /* needs to be first for thread_stack */ THD *thd; /* needs to be first for thread_stack */
uint i=0, j=0; uint i=0, j=0;
my_ulonglong cnt= 0; my_ulonglong cnt= 0;
DBUG_ENTER("event_executor_main"); DBUG_ENTER("event_executor_main");
DBUG_PRINT("event_executor_main", ("EVEX thread started")); DBUG_PRINT("event_executor_main", ("EVEX thread started"));
pthread_mutex_lock(&LOCK_evex_main_thread);
if (!scheduler_main_thread_running)
scheduler_main_thread_running= true;
else
{
DBUG_PRINT("event_executor_main", ("already running. thd_id=%d",
evex_main_thread_id));
pthread_mutex_unlock(&LOCK_evex_main_thread);
my_thread_end();
pthread_exit(0);
DBUG_RETURN(0); // Can't return anything here
}
pthread_mutex_unlock(&LOCK_evex_main_thread);
/* init memory root */ /* init memory root */
init_alloc_root(&evex_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); init_alloc_root(&evex_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
...@@ -489,7 +507,7 @@ event_executor_main(void *arg) ...@@ -489,7 +507,7 @@ event_executor_main(void *arg)
if (!evex_queue_num_elements(EVEX_EQ_NAME)) if (!evex_queue_num_elements(EVEX_EQ_NAME))
{ {
my_sleep(1000000);// sleep 1s my_sleep(100000);// sleep 0.1s
continue; continue;
} }
...@@ -652,12 +670,17 @@ event_executor_main(void *arg) ...@@ -652,12 +670,17 @@ event_executor_main(void *arg)
err_no_thd: err_no_thd:
VOID(pthread_mutex_lock(&LOCK_evex_running)); VOID(pthread_mutex_lock(&LOCK_evex_running));
evex_is_running= false; evex_is_running= false;
event_executor_running_global_var= false;
VOID(pthread_mutex_unlock(&LOCK_evex_running)); VOID(pthread_mutex_unlock(&LOCK_evex_running));
free_root(&evex_mem_root, MYF(0)); free_root(&evex_mem_root, MYF(0));
sql_print_information("SCHEDULER: Stopped."); sql_print_information("SCHEDULER: Stopped.");
#ifndef DBUG_FAULTY_THR #ifndef DBUG_FAULTY_THR
pthread_mutex_lock(&LOCK_evex_main_thread);
scheduler_main_thread_running= false;
pthread_mutex_unlock(&LOCK_evex_main_thread);
my_thread_end(); my_thread_end();
pthread_exit(0); pthread_exit(0);
#endif #endif
......
...@@ -567,28 +567,9 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) ...@@ -567,28 +567,9 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table)
et->created= table->field[EVEX_FIELD_CREATED]->val_int(); et->created= table->field[EVEX_FIELD_CREATED]->val_int();
et->modified= table->field[EVEX_FIELD_MODIFIED]->val_int(); et->modified= 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]-> table->field[EVEX_FIELD_LAST_EXECUTED]->
get_date(&et->last_executed, TIME_NO_ZERO_DATE); get_date(&et->last_executed, TIME_NO_ZERO_DATE);
#endif
last_executed_changed= false; last_executed_changed= false;
/* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */ /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */
...@@ -622,70 +603,164 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) ...@@ -622,70 +603,164 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table)
/* /*
Computes the sum of a timestamp plus interval Computes the sum of a timestamp plus interval. Presumed is that at least one
previous execution has occured.
SYNOPSIS SYNOPSIS
get_next_time(TIME *start, int interval_value, interval_type interval) get_next_time(TIME *start, int interval_value, interval_type interval)
next the sum next the sum
start add interval_value to this time start add interval_value to this time
time_now current time
i_value quantity of time type interval to add i_value quantity of time type interval to add
i_type type of interval to add (SECOND, MINUTE, HOUR, WEEK ...) i_type type of interval to add (SECOND, MINUTE, HOUR, WEEK ...)
RETURNS
0 OK
1 Error
NOTES
1) If the interval is conversible to SECOND, like MINUTE, HOUR, DAY, WEEK.
Then we use TIMEDIFF()'s implementation as underlying and number of
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 static
bool get_next_time(TIME *next, TIME *start, int i_value, interval_type i_type) bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec,
int i_value, interval_type i_type)
{ {
bool ret; bool ret;
INTERVAL interval; INTERVAL interval;
TIME tmp; TIME tmp;
longlong months=0, seconds=0;
DBUG_ENTER("get_next_time");
DBUG_PRINT("enter", ("start=%llu now=%llu", TIME_to_ulonglong_datetime(start),
TIME_to_ulonglong_datetime(time_now)));
bzero(&interval, sizeof(interval)); bzero(&interval, sizeof(interval));
switch (i_type) { switch (i_type) {
case INTERVAL_YEAR: case INTERVAL_YEAR:
interval.year= (ulong) i_value; months= i_value*12;
break; break;
case INTERVAL_QUARTER: case INTERVAL_QUARTER:
interval.month= (ulong)(i_value*3); /* Has already been converted to months */
break;
case INTERVAL_YEAR_MONTH: case INTERVAL_YEAR_MONTH:
case INTERVAL_MONTH: case INTERVAL_MONTH:
interval.month= (ulong) i_value; months= i_value;
break; break;
case INTERVAL_WEEK: case INTERVAL_WEEK:
interval.day= (ulong)(i_value*7); /* WEEK has already been converted to days */
break;
case INTERVAL_DAY: case INTERVAL_DAY:
interval.day= (ulong) i_value; seconds= i_value*24*3600;
break; break;
case INTERVAL_DAY_HOUR: case INTERVAL_DAY_HOUR:
case INTERVAL_HOUR: case INTERVAL_HOUR:
interval.hour= (ulong) i_value; seconds= i_value*3600;
break; break;
case INTERVAL_DAY_MINUTE: case INTERVAL_DAY_MINUTE:
case INTERVAL_HOUR_MINUTE: case INTERVAL_HOUR_MINUTE:
case INTERVAL_MINUTE: case INTERVAL_MINUTE:
interval.minute=i_value; seconds= i_value*60;
break; break;
case INTERVAL_DAY_SECOND: case INTERVAL_DAY_SECOND:
case INTERVAL_HOUR_SECOND: case INTERVAL_HOUR_SECOND:
case INTERVAL_MINUTE_SECOND: case INTERVAL_MINUTE_SECOND:
case INTERVAL_SECOND: case INTERVAL_SECOND:
interval.second=i_value; seconds= i_value;
break; break;
case INTERVAL_DAY_MICROSECOND: case INTERVAL_DAY_MICROSECOND:
case INTERVAL_HOUR_MICROSECOND: case INTERVAL_HOUR_MICROSECOND:
case INTERVAL_MINUTE_MICROSECOND: case INTERVAL_MINUTE_MICROSECOND:
case INTERVAL_SECOND_MICROSECOND: case INTERVAL_SECOND_MICROSECOND:
case INTERVAL_MICROSECOND: case INTERVAL_MICROSECOND:
interval.second_part=i_value; /*
We should return an error here so SHOW EVENTS/ SELECT FROM I_S.EVENTS
would give an error then.
*/
DBUG_RETURN(1);
break; break;
} }
tmp= *start; DBUG_PRINT("info", ("seconds=%ld months=%ld", seconds, months));
if (!(ret= date_add_interval(&tmp, i_type, interval))) if (seconds)
{
longlong seconds_diff;
long microsec_diff;
if (calc_time_diff(time_now, start, 1, &seconds_diff, &microsec_diff))
{
DBUG_PRINT("error", ("negative difference"));
DBUG_ASSERT(0);
}
uint multiplier= 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))
++multiplier;
interval.second= seconds * multiplier;
DBUG_PRINT("info", ("multiplier=%u interval.second=%u", multiplier,
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.
*/
/*
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.
*/
interval.month= (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+= months;
tmp= *start;
if ((ret= date_add_interval(&tmp, INTERVAL_MONTH, interval)))
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+= months;
tmp= *start;
if ((ret= date_add_interval(&tmp, INTERVAL_MONTH, interval)))
goto done;
}
*next= tmp; *next= tmp;
/* assert on that the next is after now */
DBUG_ASSERT(1==my_time_compare(next, time_now));
}
return ret; done:
DBUG_PRINT("info", ("next=%llu", TIME_to_ulonglong_datetime(next)));
DBUG_RETURN(ret);
} }
...@@ -708,6 +783,10 @@ Event_timed::compute_next_execution_time() ...@@ -708,6 +783,10 @@ Event_timed::compute_next_execution_time()
int tmp; int tmp;
DBUG_ENTER("Event_timed::compute_next_execution_time"); DBUG_ENTER("Event_timed::compute_next_execution_time");
DBUG_PRINT("enter", ("starts=%llu ends=%llu last_executed=%llu",
TIME_to_ulonglong_datetime(&starts),
TIME_to_ulonglong_datetime(&ends),
TIME_to_ulonglong_datetime(&last_executed)));
if (status == MYSQL_EVENT_DISABLED) if (status == MYSQL_EVENT_DISABLED)
{ {
...@@ -731,29 +810,14 @@ Event_timed::compute_next_execution_time() ...@@ -731,29 +810,14 @@ Event_timed::compute_next_execution_time()
} }
goto ret; goto ret;
} }
time((time_t *)&now); my_tz_UTC->gmt_sec_to_TIME(&time_now, current_thd->query_start());
my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
#ifdef ANDREY_0 DBUG_PRINT("info",("NOW=[%llu]", TIME_to_ulonglong_datetime(&time_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);
#endif
/* if time_now is after ends don't execute anymore */ /* if time_now is after ends don't execute anymore */
if (!ends_null && (tmp= my_time_compare(&ends, &time_now)) == -1) if (!ends_null && (tmp= my_time_compare(&ends, &time_now)) == -1)
{ {
DBUG_PRINT("info", ("NOW after ENDS, don't execute anymore"));
/* time_now is after ends. don't execute anymore */ /* time_now is after ends. don't execute anymore */
set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
execute_at_null= TRUE; execute_at_null= TRUE;
...@@ -781,6 +845,7 @@ Event_timed::compute_next_execution_time() ...@@ -781,6 +845,7 @@ Event_timed::compute_next_execution_time()
} }
else else
{ {
DBUG_PRINT("info", ("STARTS is future, NOW <= STARTS,sched for STARTS"));
/* /*
starts is in the future starts is in the future
time_now before starts. Scheduling for starts time_now before starts. Scheduling for starts
...@@ -799,8 +864,10 @@ Event_timed::compute_next_execution_time() ...@@ -799,8 +864,10 @@ Event_timed::compute_next_execution_time()
after m_ends set execute_at to 0. And check for on_completion after m_ends set execute_at to 0. And check for on_completion
If not set then schedule for now. If not set then schedule for now.
*/ */
DBUG_PRINT("info", ("Both STARTS & ENDS are set"));
if (!last_executed.year) if (!last_executed.year)
{ {
DBUG_PRINT("info", ("Not executed so far. Execute NOW."));
execute_at= time_now; execute_at= time_now;
execute_at_null= FALSE; execute_at_null= FALSE;
} }
...@@ -808,12 +875,15 @@ Event_timed::compute_next_execution_time() ...@@ -808,12 +875,15 @@ Event_timed::compute_next_execution_time()
{ {
TIME next_exec; TIME next_exec;
if (get_next_time(&next_exec, &last_executed, expression, interval)) DBUG_PRINT("info", ("Executed at least once"));
if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
expression, interval))
goto err; goto err;
/* There was previous execution */ /* There was previous execution */
if (my_time_compare(&ends, &next_exec) == -1) if (my_time_compare(&ends, &next_exec) == -1)
{ {
DBUG_PRINT("info", ("Next execution after ENDS. Stop executing."));
/* Next execution after ends. No more executions */ /* Next execution after ends. No more executions */
set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
execute_at_null= TRUE; execute_at_null= TRUE;
...@@ -822,6 +892,7 @@ Event_timed::compute_next_execution_time() ...@@ -822,6 +892,7 @@ Event_timed::compute_next_execution_time()
} }
else else
{ {
DBUG_PRINT("info",("Next[%llu]",TIME_to_ulonglong_datetime(&next_exec)));
execute_at= next_exec; execute_at= next_exec;
execute_at_null= FALSE; execute_at_null= FALSE;
} }
...@@ -830,18 +901,24 @@ Event_timed::compute_next_execution_time() ...@@ -830,18 +901,24 @@ Event_timed::compute_next_execution_time()
} }
else if (starts_null && ends_null) else if (starts_null && ends_null)
{ {
DBUG_PRINT("info", ("Neither STARTS nor ENDS are set"));
/* /*
Both starts and m_ends are not set, so we schedule for the next Both starts and m_ends are not set, so we schedule for the next
based on last_executed. based on last_executed.
*/ */
if (last_executed.year) if (last_executed.year)
{ {
if (get_next_time(&execute_at, &last_executed, expression, interval)) TIME next_exec;
if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
expression, interval))
goto err; goto err;
execute_at= next_exec;
DBUG_PRINT("info",("Next[%llu]",TIME_to_ulonglong_datetime(&next_exec)));
} }
else else
{ {
/* last_executed not set. Schedule the event for now */ /* last_executed not set. Schedule the event for now */
DBUG_PRINT("info", ("Execute NOW"));
execute_at= time_now; execute_at= time_now;
} }
execute_at_null= FALSE; execute_at_null= FALSE;
...@@ -851,6 +928,7 @@ Event_timed::compute_next_execution_time() ...@@ -851,6 +928,7 @@ Event_timed::compute_next_execution_time()
/* either starts or m_ends is set */ /* either starts or m_ends is set */
if (!starts_null) if (!starts_null)
{ {
DBUG_PRINT("info", ("STARTS is set"));
/* /*
- starts is set. - starts is set.
- starts is not in the future according to check made before - starts is not in the future according to check made before
...@@ -859,15 +937,24 @@ Event_timed::compute_next_execution_time() ...@@ -859,15 +937,24 @@ Event_timed::compute_next_execution_time()
*/ */
if (last_executed.year) if (last_executed.year)
{ {
if (get_next_time(&execute_at, &last_executed, expression, interval)) TIME next_exec;
DBUG_PRINT("info", ("Executed at least once."));
if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
expression, interval))
goto err; goto err;
execute_at= next_exec;
DBUG_PRINT("info",("Next[%llu]",TIME_to_ulonglong_datetime(&next_exec)));
} }
else else
{
DBUG_PRINT("info", ("Not executed so far. Execute at STARTS"));
execute_at= starts; execute_at= starts;
}
execute_at_null= FALSE; execute_at_null= FALSE;
} }
else else
{ {
DBUG_PRINT("info", ("STARTS is not set. ENDS is set"));
/* /*
- m_ends is set - m_ends is set
- m_ends is after time_now or is equal - m_ends is after time_now or is equal
...@@ -881,11 +968,13 @@ Event_timed::compute_next_execution_time() ...@@ -881,11 +968,13 @@ Event_timed::compute_next_execution_time()
{ {
TIME next_exec; TIME next_exec;
if (get_next_time(&next_exec, &last_executed, expression, interval)) if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
expression, interval))
goto err; goto err;
if (my_time_compare(&ends, &next_exec) == -1) if (my_time_compare(&ends, &next_exec) == -1)
{ {
DBUG_PRINT("info", ("Next execution after ENDS. Stop executing."));
set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
execute_at_null= TRUE; execute_at_null= TRUE;
if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP) if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
...@@ -893,6 +982,8 @@ Event_timed::compute_next_execution_time() ...@@ -893,6 +982,8 @@ Event_timed::compute_next_execution_time()
} }
else else
{ {
DBUG_PRINT("info", ("Next[%llu]",
TIME_to_ulonglong_datetime(&next_exec)));
execute_at= next_exec; execute_at= next_exec;
execute_at_null= FALSE; execute_at_null= FALSE;
} }
...@@ -901,9 +992,10 @@ Event_timed::compute_next_execution_time() ...@@ -901,9 +992,10 @@ Event_timed::compute_next_execution_time()
goto ret; goto ret;
} }
ret: ret:
DBUG_PRINT("info", ("ret=0"));
DBUG_RETURN(false); DBUG_RETURN(false);
err: err:
DBUG_PRINT("info", ("ret=1"));
DBUG_RETURN(true); DBUG_RETURN(true);
} }
...@@ -1437,6 +1529,7 @@ Event_timed::spawn_now(void * (*thread_func)(void*)) ...@@ -1437,6 +1529,7 @@ Event_timed::spawn_now(void * (*thread_func)(void*))
int ret= EVENT_EXEC_STARTED; int ret= EVENT_EXEC_STARTED;
static uint exec_num= 0; static uint exec_num= 0;
DBUG_ENTER("Event_timed::spawn_now"); DBUG_ENTER("Event_timed::spawn_now");
DBUG_PRINT("info", ("this=0x%lx", this));
DBUG_PRINT("info", ("[%s.%s]", dbname.str, name.str)); DBUG_PRINT("info", ("[%s.%s]", dbname.str, name.str));
VOID(pthread_mutex_lock(&this->LOCK_running)); VOID(pthread_mutex_lock(&this->LOCK_running));
......
...@@ -772,81 +772,6 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, ...@@ -772,81 +772,6 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs,
} }
/*
Calculate difference between two datetime values as seconds + microseconds.
SYNOPSIS
calc_time_diff()
l_time1 - TIME/DATE/DATETIME value
l_time2 - TIME/DATE/DATETIME value
l_sign - 1 absolute values are substracted,
-1 absolute values are added.
seconds_out - Out parameter where difference between
l_time1 and l_time2 in seconds is stored.
microseconds_out- Out parameter where microsecond part of difference
between l_time1 and l_time2 is stored.
NOTE
This function calculates difference between l_time1 and l_time2 absolute
values. So one should set l_sign and correct result if he want to take
signs into account (i.e. for TIME values).
RETURN VALUES
Returns sign of difference.
1 means negative result
0 means positive result
*/
static bool calc_time_diff(TIME *l_time1, TIME *l_time2, int l_sign,
longlong *seconds_out, long *microseconds_out)
{
long days;
bool neg;
longlong microseconds;
/*
We suppose that if first argument is MYSQL_TIMESTAMP_TIME
the second argument should be TIMESTAMP_TIME also.
We should check it before calc_time_diff call.
*/
if (l_time1->time_type == MYSQL_TIMESTAMP_TIME) // Time value
days= (long)l_time1->day - l_sign * (long)l_time2->day;
else
{
days= calc_daynr((uint) l_time1->year,
(uint) l_time1->month,
(uint) l_time1->day);
if (l_time2->time_type == MYSQL_TIMESTAMP_TIME)
days-= l_sign * (long)l_time2->day;
else
days-= l_sign*calc_daynr((uint) l_time2->year,
(uint) l_time2->month,
(uint) l_time2->day);
}
microseconds= ((longlong)days*LL(86400) +
(longlong)(l_time1->hour*3600L +
l_time1->minute*60L +
l_time1->second) -
l_sign*(longlong)(l_time2->hour*3600L +
l_time2->minute*60L +
l_time2->second)) * LL(1000000) +
(longlong)l_time1->second_part -
l_sign*(longlong)l_time2->second_part;
neg= 0;
if (microseconds < 0)
{
microseconds= -microseconds;
neg= 1;
}
*seconds_out= microseconds/1000000L;
*microseconds_out= (long) (microseconds%1000000L);
return neg;
}
longlong Item_func_period_add::val_int() longlong Item_func_period_add::val_int()
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
...@@ -2032,16 +1957,13 @@ bool Item_date_add_interval::get_date(TIME *ltime, uint fuzzy_date) ...@@ -2032,16 +1957,13 @@ bool Item_date_add_interval::get_date(TIME *ltime, uint fuzzy_date)
INTERVAL interval; INTERVAL interval;
if (args[0]->get_date(ltime, TIME_NO_ZERO_DATE) || if (args[0]->get_date(ltime, TIME_NO_ZERO_DATE) ||
get_interval_value(args[1],int_type,&value,&interval)) get_interval_value(args[1], int_type, &value, &interval))
goto null_date; return (null_value=1);
if (date_sub_interval) if (date_sub_interval)
interval.neg = !interval.neg; interval.neg = !interval.neg;
return (null_value= date_add_interval(ltime, int_type, interval)); return (null_value= date_add_interval(ltime, int_type, interval));
null_date:
return (null_value=1);
} }
......
...@@ -1544,6 +1544,8 @@ void make_truncated_value_warning(THD *thd, const char *str_val, ...@@ -1544,6 +1544,8 @@ void make_truncated_value_warning(THD *thd, const char *str_val,
const char *field_name); const char *field_name);
bool date_add_interval(TIME *ltime, interval_type int_type, INTERVAL interval); bool date_add_interval(TIME *ltime, interval_type int_type, INTERVAL interval);
bool calc_time_diff(TIME *l_time1, TIME *l_time2, int l_sign,
longlong *seconds_out, long *microseconds_out);
extern DATE_TIME_FORMAT *date_time_format_make(timestamp_type format_type, extern DATE_TIME_FORMAT *date_time_format_make(timestamp_type format_type,
const char *format_str, const char *format_str,
......
...@@ -3956,7 +3956,7 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) ...@@ -3956,7 +3956,7 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
if (!(!wild || !wild[0] || !wild_compare(et.name.str, wild, 0))) if (!(!wild || !wild[0] || !wild_compare(et.name.str, wild, 0)))
DBUG_RETURN(0); DBUG_RETURN(0);
//->field[0] is EVENT_CATALOG and is by default NULL /* ->field[0] is EVENT_CATALOG and is by default NULL */
sch_table->field[1]->store(et.dbname.str, et.dbname.length, scs); sch_table->field[1]->store(et.dbname.str, et.dbname.length, scs);
sch_table->field[2]->store(et.name.str, et.name.length, scs); sch_table->field[2]->store(et.name.str, et.name.length, scs);
...@@ -3976,12 +3976,9 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) ...@@ -3976,12 +3976,9 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
if (et.expression) if (et.expression)
{ {
String show_str; String show_str;
//type /* type */
sch_table->field[5]->store(STRING_WITH_LEN("RECURRING"), scs); sch_table->field[5]->store(STRING_WITH_LEN("RECURRING"), scs);
/* execute_at */
sch_table->field[6]->set_null();
/* interval_value */
//interval_type
if (event_reconstruct_interval_expression(&show_str, et.interval, if (event_reconstruct_interval_expression(&show_str, et.interval,
et.expression)) et.expression))
DBUG_RETURN(1); DBUG_RETURN(1);
...@@ -4034,9 +4031,10 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) ...@@ -4034,9 +4031,10 @@ fill_events_copy_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table)
sch_table->field[15]->store_time(&time, MYSQL_TIMESTAMP_DATETIME); sch_table->field[15]->store_time(&time, MYSQL_TIMESTAMP_DATETIME);
if (et.last_executed.year) if (et.last_executed.year)
{
sch_table->field[16]->set_notnull();
sch_table->field[16]->store_time(&et.last_executed,MYSQL_TIMESTAMP_DATETIME); sch_table->field[16]->store_time(&et.last_executed,MYSQL_TIMESTAMP_DATETIME);
else }
sch_table->field[16]->set_null();
sch_table->field[17]->store(et.comment.str, et.comment.length, scs); sch_table->field[17]->store(et.comment.str, et.comment.length, scs);
......
...@@ -833,4 +833,80 @@ bool date_add_interval(TIME *ltime, interval_type int_type, INTERVAL interval) ...@@ -833,4 +833,80 @@ bool date_add_interval(TIME *ltime, interval_type int_type, INTERVAL interval)
} }
/*
Calculate difference between two datetime values as seconds + microseconds.
SYNOPSIS
calc_time_diff()
l_time1 - TIME/DATE/DATETIME value
l_time2 - TIME/DATE/DATETIME value
l_sign - 1 absolute values are substracted,
-1 absolute values are added.
seconds_out - Out parameter where difference between
l_time1 and l_time2 in seconds is stored.
microseconds_out- Out parameter where microsecond part of difference
between l_time1 and l_time2 is stored.
NOTE
This function calculates difference between l_time1 and l_time2 absolute
values. So one should set l_sign and correct result if he want to take
signs into account (i.e. for TIME values).
RETURN VALUES
Returns sign of difference.
1 means negative result
0 means positive result
*/
bool
calc_time_diff(TIME *l_time1, TIME *l_time2, int l_sign, longlong *seconds_out,
long *microseconds_out)
{
long days;
bool neg;
longlong microseconds;
/*
We suppose that if first argument is MYSQL_TIMESTAMP_TIME
the second argument should be TIMESTAMP_TIME also.
We should check it before calc_time_diff call.
*/
if (l_time1->time_type == MYSQL_TIMESTAMP_TIME) // Time value
days= (long)l_time1->day - l_sign * (long)l_time2->day;
else
{
days= calc_daynr((uint) l_time1->year,
(uint) l_time1->month,
(uint) l_time1->day);
if (l_time2->time_type == MYSQL_TIMESTAMP_TIME)
days-= l_sign * (long)l_time2->day;
else
days-= l_sign*calc_daynr((uint) l_time2->year,
(uint) l_time2->month,
(uint) l_time2->day);
}
microseconds= ((longlong)days*LL(86400) +
(longlong)(l_time1->hour*3600L +
l_time1->minute*60L +
l_time1->second) -
l_sign*(longlong)(l_time2->hour*3600L +
l_time2->minute*60L +
l_time2->second)) * LL(1000000) +
(longlong)l_time1->second_part -
l_sign*(longlong)l_time2->second_part;
neg= 0;
if (microseconds < 0)
{
microseconds= -microseconds;
neg= 1;
}
*seconds_out= microseconds/1000000L;
*microseconds_out= (long) (microseconds%1000000L);
return neg;
}
#endif #endif
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