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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions