Skip to content

replace % with mod() SQL function to avoid invalid double-percentage … #923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

AngryUbuntuNerd
Copy link

fixes #922

invalid SQL before:

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))))

new SQL:

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 10:42:42.813538'
			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 MOD(((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))))

@auvipy auvipy requested review from Copilot and auvipy July 29, 2025 15:15
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a SQL generation issue where Django was producing invalid double-percentage (%%) operators in MySQL queries. The fix replaces the Python modulo operator (%) with Django's Func class using the SQL MOD() function to ensure proper SQL syntax.

Key Changes

  • Replaces Python modulo operator with Django's Func class using MOD function
  • Wraps the expression in ExpressionWrapper with proper output field type
  • Adds necessary imports for ExpressionWrapper and Func

Comment on lines +19 to +20
from django.db.models import (Case, ExpressionWrapper, F, Func, IntegerField,
Q, When)
Copy link
Preview

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The import statement is split across multiple lines in an inconsistent format. Consider keeping all imports on the same line or using a more consistent multi-line format with proper indentation.

Suggested change
from django.db.models import (Case, ExpressionWrapper, F, Func, IntegerField,
Q, When)
from django.db.models import (
Case, ExpressionWrapper, F, Func, IntegerField, Q, When
)

Copilot uses AI. Check for mistakes.

Copy link

codecov bot commented Jul 29, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.19%. Comparing base (f1f239c) to head (19e59c3).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #923   +/-   ##
=======================================
  Coverage   88.19%   88.19%           
=======================================
  Files          32       32           
  Lines        1008     1008           
  Branches      105      105           
=======================================
  Hits          889      889           
  Misses        101      101           
  Partials       18       18           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Member

@auvipy auvipy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is nit pissible to modify the relevant tests as well?

@AngryUbuntuNerd
Copy link
Author

can you give me some guidance there:

  1. which test needs modifying?
  2. how do I run the tests locally? the README does not seem to say

@auvipy
Copy link
Member

auvipy commented Aug 3, 2025

this should be the primary file https://github.com/celery/django-celery-beat/blob/main/t/unit/test_schedulers.py

and for tests there are docker and tox for running tests locally

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: Invalid MySQL query using version 2.8.1
2 participants