Tuesday, October 15, 2024

ORM Bug from extending the `Query` class

Hi all,

We wrote some custom logic that extended the `Query` class. It worked fine in 3.2 but not for 4.1, and it started producing invalid SQL. We filed a ticket (https://code.djangoproject.com/ticket/35836) but haven't made any progress since `Query` is an undocumented API.

Our use case is an automatic site filter - objects belonging to different customer sites are automatically filtered to the current site, so each queryset doesn't have to be explicitly filtered. We did this at the Query level rather than filtering the default manager's `get_queryset`. This has the useful effect that our DRF views can define a queryset attribute on their class definition (where there isn't a 'current' site) and the query will be filtered when it is actually executed (in a request context on a particular site)

How should we change our usage of `Query` to fix the bug? Below is the contents of the ticket, including a reproduction.

Thanks,
Ben Pearman

___

We have observed a bug in the ORM where invalid SQL can be produced.

A reproduction can be seen here:
https://github.com/benpearman/django-orm-bug
And specifically:
https://github.com/benpearman/django-orm-bug/blob/master/demo/models.py

It is caused with the following steps:

  1. Subclassing `django.db.models.sql.Query` and using it in a Queryset mixin
  2. Creating a queryset with an `exclude()` expression which includes a subquery. EG `Classroom.objects.exclude(student_set__id=student_A.id)`
  3. Then before calling `super().get_compiler` from our subclassed Query:
    • Clone the query
    • Adding a simple expression via `cloned_query.add_q` eg `cloned_query.add_q(Q(school_id=1))`
    • Passing the cloned query into `super().get_compiler`
  4. Then evaluate the queryset, which produces invalid SQL and raises `django.db.utils.OperationalError`

Note the issue happens when `get_compiler` is invoked for the subquery, not on the main query itself.

This was working for us in Django 3.2, but not for Django 4.1

git bisect shows the breakage was introduced by this commit:
https://github.com/django/django/commit/14c8504a37afad96ab93cf82f47b13bcc4d00621

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/af1c619d-eaed-4ad8-896f-849d86f9a674n%40googlegroups.com.

No comments:

Post a Comment