Skip to content

fix Subquery annotation detection in dnfs() for Django 6.0+#508

Merged
Suor merged 3 commits intoSuor:masterfrom
audriusm:fix/django6-subquery-annotation-detection
Mar 4, 2026
Merged

fix Subquery annotation detection in dnfs() for Django 6.0+#508
Suor merged 3 commits intoSuor:masterfrom
audriusm:fix/django6-subquery-annotation-detection

Conversation

@audriusm
Copy link
Contributor

@audriusm audriusm commented Mar 4, 2026

Problem

Django 6.0 changed how Subquery expressions are stored in qs.query.annotations. In Django 5.x, annotation values remain as Subquery instances. In Django 6.0+, they are resolved into raw django.db.models.sql.query.Query objects during .annotate().

The annotation dependency detection in tree.dnfs() only checks for Subquery instances:

subqueries = (query_dnf(getattr(q, 'query', None))
              for q in qs.query.annotations.values() if isinstance(q, Subquery))

On Django 6, isinstance(q, Subquery) returns False for the resolved Query objects, so cross-table Subquery dependencies are completely invisible to cache invalidation.

This means any queryset with Subquery annotations on a cacheops-enabled model won't invalidate its cache when the subqueried table is modified - the cached result stays stale until timeout.

How I found this

Came up while upgrading a project from Django 5.2 to 6.0. An endpoint annotating a cached model with a Subquery on a different table was returning stale data after writes to the subqueried table. Inspecting dnfs() output confirmed it only reported the primary table - the subqueried table was missing entirely.

Fix

Check for both Subquery instances (Django <6) and resolved Query objects (Django 6+) when scanning annotations for table dependencies.

The existing test_365 covers this scenario and would fail on Django 6 without this fix.

Added test_366 test to verify this fix.

@audriusm audriusm force-pushed the fix/django6-subquery-annotation-detection branch 3 times, most recently from 10fae3c to ac5468a Compare March 4, 2026 08:29
Django 6.0 resolves Subquery annotations into raw Query objects
in qs.query.annotations. The existing isinstance(q, Subquery)
check misses these, causing cross-table Subquery dependencies
to be invisible to cache invalidation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@audriusm audriusm force-pushed the fix/django6-subquery-annotation-detection branch from ac5468a to 8d645cb Compare March 4, 2026 08:33
@Suor Suor merged commit e3a7117 into Suor:master Mar 4, 2026
8 of 9 checks passed
@Suor
Copy link
Owner

Suor commented Mar 4, 2026

Merged, thanks

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.

2 participants