Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions mysql-test/main/mdev38632_alter_onetime_event.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
SET GLOBAL event_scheduler = ON;
CREATE TABLE test.event_log (id INT AUTO_INCREMENT PRIMARY KEY, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
# Step 1: Create a one-time AT event with ON COMPLETION PRESERVE
CREATE EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND
ON COMPLETION PRESERVE ENABLE
DO INSERT INTO test.event_log(id) VALUES (NULL);
# Wait for the event to execute
# Verify: event executed, status is now DISABLED (preserved but done)
SELECT status, on_completion, last_executed IS NOT NULL AS has_last_exec
FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';
status on_completion has_last_exec
DISABLED PRESERVE 1
# Step 2: ALTER EVENT to reschedule with explicit ENABLE
# This is the customer's scenario from the JIRA report.
ALTER EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND
ON COMPLETION PRESERVE ENABLE;
# After ALTER with ENABLE and a new schedule, last_executed should be cleared.
SELECT status, last_executed IS NOT NULL AS has_last_exec
FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';
status has_last_exec
ENABLED 0
# The event should fire again after being rescheduled.
# Step 3: Verify event fires after server restart (the customer scenario).
ALTER EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 3 SECOND
ON COMPLETION PRESERVE ENABLE;
# last_executed should be cleared when schedule changes.
SELECT status, last_executed IS NOT NULL AS has_last_exec
FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';
status has_last_exec
ENABLED 0
# Restart the server to trigger full reload from mysql.event.
# restart
SET GLOBAL event_scheduler = ON;
# The event should fire after server restart.
# Step 4: User-disabled event stays DISABLED after reschedule without ENABLE
ALTER EVENT test.mdev38632 DISABLE;
ALTER EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 2 HOUR;
# Status remains DISABLED — explicit ENABLE is required to reschedule.
SELECT status FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';
status
DISABLED
# Step 5: ALTER that doesn't change schedule should not clear last_executed
DROP EVENT test.mdev38632;
CREATE EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND
ON COMPLETION PRESERVE ENABLE
DO INSERT INTO test.event_log(id) VALUES (NULL);
# Event executed and is DISABLED. ALTER body only (no schedule change).
ALTER EVENT test.mdev38632
DO INSERT INTO test.event_log(id) VALUES (NULL);
# Status should remain DISABLED (no schedule change)
SELECT status FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';
status
DISABLED
# Cleanup
DROP EVENT test.mdev38632;
DROP TABLE test.event_log;
SET GLOBAL event_scheduler = OFF;
97 changes: 97 additions & 0 deletions mysql-test/main/mdev38632_alter_onetime_event.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#
# MDEV-38632: ALTER EVENT doesn't run a one-time (AT) event after its first
# execution when ON COMPLETION PRESERVE is used.
#
# After an AT event with ON COMPLETION PRESERVE executes, the scheduler sets
# status=DISABLED and last_executed in mysql.event. A subsequent ALTER EVENT
# with explicit ENABLE and a new schedule should allow the event to fire again.
#
# The fix clears last_executed when execute_at changes, and only disables
# in compute_next_execution_time() if last_executed >= execute_at.
#

--source include/not_embedded.inc

SET GLOBAL event_scheduler = ON;
--source include/running_event_scheduler.inc

CREATE TABLE test.event_log (id INT AUTO_INCREMENT PRIMARY KEY, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

--echo # Step 1: Create a one-time AT event with ON COMPLETION PRESERVE
CREATE EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND
ON COMPLETION PRESERVE ENABLE
DO INSERT INTO test.event_log(id) VALUES (NULL);

--echo # Wait for the event to execute
let $wait_condition = SELECT COUNT(*) = 1 FROM test.event_log;
--source include/wait_condition.inc

--echo # Verify: event executed, status is now DISABLED (preserved but done)
SELECT status, on_completion, last_executed IS NOT NULL AS has_last_exec
FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';

--echo # Step 2: ALTER EVENT to reschedule with explicit ENABLE
--echo # This is the customer's scenario from the JIRA report.
ALTER EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND
ON COMPLETION PRESERVE ENABLE;

--echo # After ALTER with ENABLE and a new schedule, last_executed should be cleared.
SELECT status, last_executed IS NOT NULL AS has_last_exec
FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';

--echo # The event should fire again after being rescheduled.
let $wait_condition = SELECT COUNT(*) = 2 FROM test.event_log;
--source include/wait_condition.inc

--echo # Step 3: Verify event fires after server restart (the customer scenario).
ALTER EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 3 SECOND
ON COMPLETION PRESERVE ENABLE;

--echo # last_executed should be cleared when schedule changes.
SELECT status, last_executed IS NOT NULL AS has_last_exec
FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';

--echo # Restart the server to trigger full reload from mysql.event.
--source include/restart_mysqld.inc
SET GLOBAL event_scheduler = ON;
--source include/running_event_scheduler.inc

--echo # The event should fire after server restart.
--let $wait_timeout = 10
let $wait_condition = SELECT COUNT(*) = 3 FROM test.event_log;
--source include/wait_condition.inc

--echo # Step 4: User-disabled event stays DISABLED after reschedule without ENABLE
ALTER EVENT test.mdev38632 DISABLE;

ALTER EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 2 HOUR;

--echo # Status remains DISABLED — explicit ENABLE is required to reschedule.
SELECT status FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';

--echo # Step 5: ALTER that doesn't change schedule should not clear last_executed
DROP EVENT test.mdev38632;
CREATE EVENT test.mdev38632
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND
ON COMPLETION PRESERVE ENABLE
DO INSERT INTO test.event_log(id) VALUES (NULL);

let $wait_condition = SELECT COUNT(*) = 4 FROM test.event_log;
--source include/wait_condition.inc

--echo # Event executed and is DISABLED. ALTER body only (no schedule change).
ALTER EVENT test.mdev38632
DO INSERT INTO test.event_log(id) VALUES (NULL);

--echo # Status should remain DISABLED (no schedule change)
SELECT status FROM mysql.event WHERE db = 'test' AND name = 'mdev38632';

--echo # Cleanup
DROP EVENT test.mdev38632;
DROP TABLE test.event_log;
SET GLOBAL event_scheduler = OFF;
--source include/check_events_off.inc
8 changes: 6 additions & 2 deletions sql/event_data_objects.cc
Original file line number Diff line number Diff line change
Expand Up @@ -948,8 +948,12 @@ Event_queue_element::compute_next_execution_time()
/* If one-time, no need to do computation */
if (!expression)
{
/* Let's check whether it was executed */
if (last_executed)
/*
Check whether the event was already executed for the current schedule.
If execute_at was changed (via ALTER EVENT) to a time after
last_executed, the event should still be considered pending (MDEV-38632).
*/
if (last_executed && last_executed >= execute_at)
{
DBUG_PRINT("info",("One-time event %s.%s of was already executed",
dbname.str, name.str));
Expand Down
24 changes: 24 additions & 0 deletions sql/event_db_repository.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,30 @@ mysql_event_fill_row(THD *thd,
MYSQL_TIME time;
my_tz_OFFSET0->gmt_sec_to_TIME(&time, et->execute_at);

/*
MDEV-38632: When ALTER EVENT changes execute_at, clear last_executed.
A new execute_at means the event hasn't been executed for this
schedule yet. Without this, compute_next_execution_time() would
see the stale last_executed and disable the event on reload.

Only clear when execute_at actually changed. Compare the new value
against the stored one before overwriting.
*/
if (is_update)
{
bool schedule_changed= true;
MYSQL_TIME old_execute_at;

if (!fields[ET_FIELD_EXECUTE_AT]->is_null() &&
!fields[ET_FIELD_EXECUTE_AT]->get_date(&old_execute_at,
TIME_NO_ZERO_DATE |
thd->temporal_round_mode()))
schedule_changed= my_time_compare(&time, &old_execute_at) != 0;

if (schedule_changed)
fields[ET_FIELD_LAST_EXECUTED]->set_null();
}

fields[ET_FIELD_EXECUTE_AT]->set_notnull();
fields[ET_FIELD_EXECUTE_AT]->store_time(&time);
}
Expand Down