#25284 closed Cleanup/optimization (fixed)
QuerySet.filter(related_id=queryset) no longer does implicit __in lookup
Reported by: | no | Owned by: | nobody |
---|---|---|---|
Component: | Documentation | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Accepted | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Consider the following:
Model.objects.filter(related_id = RelatedModel.objects.all())
In django 1.8, the resulting query contains "related_id IN (SELECT id FROM ...)"
In django 1.9, the "IN" is changed to an "=", which causes the query to break in MySql and Postgres.
Is this an intentional change? If so, it needs to be documented as a breaking/backwards in-compatible change.
Test case:
class Egg(models.Model): class Meta: app_label = 'spam' value = models.IntegerField() class Spam(models.Model): class Meta: app_label = 'spam' egg = models.ForeignKey(Egg) class TestAssign(TestCase): def test_subquery(self): egg1 = Egg.objects.create(value=1) egg11 = Egg.objects.create(value=1) Spam.objects.create(egg=egg1) Spam.objects.create(egg=egg11) eggs1=Egg.objects.filter(value=1) q = Spam.objects.filter(egg__in=eggs1) self.assertEqual(2, len(list(q)))
Attachments (2)
Change History (16)
comment:1 by , 9 years ago
by , 9 years ago
Attachment: | 25284-test.diff added |
---|
comment:2 by , 9 years ago
Oh, need to remove the "_ _in".
This line needs to change:
q = Spam.objects.filter(egg__in=eggs1)
To:
q = Spam.objects.filter(egg=eggs1)
And in the patch:
+ self.assertEqual(len(Annotation.objects.filter(tag__in=qs1)), 1)
Should be:
+ self.assertEqual(len(Annotation.objects.filter(tag=qs1)), 1)
comment:4 by , 9 years ago
Bisected to b68212f539f206679580afbfd008e7d329c9cd31. It seems to me the implicit __in
lookup probably only worked by accident but other opinions are welcome.
comment:5 by , 9 years ago
I agree that it worked by accident. My opinion is that it's changed to be explicitly not allowed so the behaviour doesn't differ across backends, and also documented as a breaking change.
comment:6 by , 9 years ago
This seems to have been non-documented and non-tested feature (probably accidental, too). Changing the behavior definitely counts as a bug fix (even more for cases where doing qs.filter(related__range=otherqs)
would result in an IN lookup).
Lets advertise this as loudly as we can in the release notes as a breaking change. It is going to be hard to find these cases in your code manually, but I think 99% of such cases will result in an error in 1.9, as some_id = (select id from other model) will result in an error if there are more than one id returned by the subquery.
comment:7 by , 9 years ago
Component: | Database layer (models, ORM) → Documentation |
---|---|
Has patch: | set |
Summary: | Regression when filtering by subquery → QuerySet.filter(related_id=queryset) no longer does implicit __in lookup |
Triage Stage: | Unreviewed → Accepted |
Type: | Bug → Cleanup/optimization |
comment:8 by , 9 years ago
What about explicitly forbidding it? Currently, it only errors in Postgresql. With Mysql it will error if the subquery returns more than one result (but not when there's only one). And with SQLite, it doesn't error at all.
comment:9 by , 9 years ago
That might be feasible as a separate enhancement. After a quick look, I couldn't determine if the implementation would be trivial or not.
I couldn't reproduce the behavior you describe using your models and tests, or with the attached test for Django's test suite.