Skip to content

Bug: Invalid MySQL query using version 2.8.1 #922

@AngryUbuntuNerd

Description

@AngryUbuntuNerd

I am using these components:

  • celery 5.4.0
  • django-celery-beat 2.8.1
  • django-celery-results 2.6.0
  • django 5.2
  • MySQL 5.7.44
  • Python 3.12.8

and can observe errors when starting up Celery Beat and having at least one periodic task active. This error did not get raised in 2.8.0.

Steps to reproduce:

  1. Have a basic Django setup
  2. Start MySQL
  3. Run Django migrations: ./manage.py migrate
  4. Start Beat: celery -A myapp beat --scheduler django_celery_beat.schedulers:DatabaseScheduler to create the celery.backend_cleanup task
  5. Stop and restart Beat
  6. Observe this error:
...
[2025-07-28 15:11:14,380: WARNING/MainProcess] raise get_mysql_exception(
[2025-07-28 15:11:14,380: WARNING/MainProcess] django.db.utils
[2025-07-28 15:11:14,380: WARNING/MainProcess] .
[2025-07-28 15:11:14,380: WARNING/MainProcess] ProgrammingError
[2025-07-28 15:11:14,380: WARNING/MainProcess] : 
[2025-07-28 15:11:14,380: WARNING/MainProcess] (1064, "1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '% 24) ELSE CAST(U0.`hour` AS signed integer) END IN (13, 14, 15, 16, 17, 4)))) A' at line 1", '42000')

Using the general query log and --log-raw option of MySQL I identified the issue in this SQL query:

SELECT
	`django_celery_beat_periodictask`.`id`,
	`django_celery_beat_periodictask`.`name`,
	`django_celery_beat_periodictask`.`task`,
	`django_celery_beat_periodictask`.`interval_id`,
	`django_celery_beat_periodictask`.`crontab_id`,
	`django_celery_beat_periodictask`.`solar_id`,
	`django_celery_beat_periodictask`.`clocked_id`,
	`django_celery_beat_periodictask`.`args`,
	`django_celery_beat_periodictask`.`kwargs`,
	`django_celery_beat_periodictask`.`queue`,
	`django_celery_beat_periodictask`.`exchange`,
	`django_celery_beat_periodictask`.`routing_key`,
	`django_celery_beat_periodictask`.`headers`,
	`django_celery_beat_periodictask`.`priority`,
	`django_celery_beat_periodictask`.`expires`,
	`django_celery_beat_periodictask`.`expire_seconds`,
	`django_celery_beat_periodictask`.`one_off`,
	`django_celery_beat_periodictask`.`start_time`,
	`django_celery_beat_periodictask`.`enabled`,
	`django_celery_beat_periodictask`.`last_run_at`,
	`django_celery_beat_periodictask`.`total_run_count`,
	`django_celery_beat_periodictask`.`date_changed`,
	`django_celery_beat_periodictask`.`description`
FROM
	`django_celery_beat_periodictask`
LEFT OUTER JOIN `django_celery_beat_clockedschedule` ON
	(`django_celery_beat_periodictask`.`clocked_id` = `django_celery_beat_clockedschedule`.`id`)
WHERE
	(`django_celery_beat_periodictask`.`enabled` = 1
		AND NOT (((`django_celery_beat_clockedschedule`.`clocked_time` > '2025-07-29 14:05:32.092804'
			AND `django_celery_beat_clockedschedule`.`clocked_time` IS NOT NULL
			AND `django_celery_beat_periodictask`.`clocked_id` IS NOT NULL)
		OR (`django_celery_beat_periodictask`.`crontab_id` IS NOT NULL
			AND `django_celery_beat_periodictask`.`crontab_id` IN (
			SELECT
				U0.`id` AS `id`
			FROM
				`django_celery_beat_crontabschedule` U0
			WHERE
				(U0.`hour` REGEXP BINARY '^\\d+$'
					AND NOT (CASE
						WHEN (U0.`timezone` = 'UTC') THEN (((CAST(U0.`hour` AS signed integer) + 0) + 24) %% 24)
						ELSE CAST(U0.`hour` AS signed integer)
					END IN (12, 13, 14, 15, 16, 4))))
				AND `django_celery_beat_periodictask`.`crontab_id` IS NOT NULL))))

Notice the double %% being semantically incorrect, its meant to be a single %. This seems to stem from

but I am unsure where the conversion from % to %% is happening.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions