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
Fixed #25534, Fixed #31639 -- Added support for transform references in expressions. #13685
Conversation
ba797ed
to
59f052f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a feeling this PR does more than solve ticket-25534 for the aggregation case.
This was a larger problem regarding all transform references as detailed in ticket-31639 which makes be believe this PR should be renamed Added support for transform references in expressions.
.
@Ian-Foote did you test this out on anything but the simple aggregation case? I think we should also test F
and OuterRef
support for transform resolution as detailed in ticket-31639 because these also go through resolve_ref
and setup_joins
.
You're right, I think this does address ticket-31639 too. I actually saw that ticket first via twitter, which inspired me to see if this could be fixed instead of just raising an error as that ticket suggests, especially since I've had this itch myself before too. |
59f052f
to
6053fa3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ian-Foote Thanks 👍 This is brilliant 💎 I left some comments.
We should update docs and add versionchanged
annotations to:
OuterRef()
,F()
, and- Aggregation functions docs.
Can we add tests for index and slice transforms for ArrayField()
and for key transforms for HStoreField()
?
Can we also add tests for other expressions and for chaining transforms?, e.g. def test_chaining_transforms(self):
Author.objects.create(name=' John ')
Author.objects.create(name='Rhonda')
with register_lookup(CharField, Trim), register_lookup(CharField, Length):
for expr in [Length('name__trim'), F('name__trim__length')]:
self.assertCountEqual(
Author.objects.annotate(length=expr).values('name', 'length'),
[{'name': ' John ', 'length': 4}, {'name': 'Rhonda', 'length': 6}],
) |
9b0c504
to
36303d3
Compare
docs/topics/db/queries.txt
Outdated
>>> Entry.objects.values('pub_date__year').annotate( | ||
top_rating=Subquery( | ||
Entry.objects.filter( | ||
pub_date__year=OuterRef('pub_date__year') | ||
).order_by('-rating').values('rating')[:1] | ||
), | ||
total_comments=Sum('number_of_comments'), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't entirely like this example - it's a bit more complicated than would be ideal.
I considered doing just:
>>> Entry.objects.annotate(
top_rating=Subquery(
Entry.objects.filter(
pub_date__year=OuterRef('pub_date__year')
).order_by('-rating').values('rating')[:1]
),
)
But I don't like that this leads to duplicate rows in the resulting queryset - we get one for each Entry
sharing pub_date__year
. The example with Sum
consolidates these into just one row.
I'm not sure explaining these nuances is in scope here - I'd prefer an example that completely avoids them.
36303d3
to
4c0b40a
Compare
Thinking about this a bit more - I think all my tests for this have been using transforms and I'm not sure the idea makes sense for lookups. Can someone think of an example where a lookup would be used, or should I drop mentions of lookups from the documentation? |
True, good catch 🎯, let's chop "lookups". django/django/db/models/sql/query.py Lines 1750 to 1752 in c70cd2a
|
4c0b40a
to
de7edab
Compare
de7edab
to
1e4a1c2
Compare
@Ian-Foote Thanks for updates 👍 I pushed small edits to tests. I'm going to check docs later today. |
1e4a1c2
to
005f124
Compare
005f124
to
9041888
Compare
I've tweaked the first commit message to stop mentioning lookups and squashed the commits while I was at it. |
@Ian-Foote Thanks 👍 I added some tests for key transforms for |
@felixxm That was a tricky one! It turns out that filtering with an I also tweaked the test so the filter actually served a purpose. |
86a9bf9
to
abbd594
Compare
09fd571
to
38213c7
Compare
@Ian-Foote Thanks for updates 👍 I found one more case that doesn't work: self.assertSequenceEqual(
RelatedJSONModel.objects.annotate(
key=F('value__d'),
related=F('json_model'),
chain=F('key__1'),
expr=Cast('key', models.JSONField()),
).filter(chain=F('related__value__d__0')),
[related_obj],
) it raises RelatedJSONModel.objects.annotate(
related=F('json_model'),
).annotate(chain=F('related__value') raises |
I agree - I think fixing this one is a fair bit of work and I'd prefer to handle it separately. |
My mistake, this behavior is well documented: "When referencing relational fields such as ForeignKey, F() returns the primary key value rather than a model instance:" I'm going to work on final edits. |
I think it's something that could be made to work, but I'm definitely happy to leave it out of this ticket/PR. |
5c14183
to
5cea9f9
Compare
@Ian-Foote I pushed edits to docs, and added tests for windows expressions, Unfortunately |
I will try to remove: django/django/db/models/sql/query.py Lines 1717 to 1718 in e46ca51
it looks redundant and unnecessary (\cc @charettes). |
Does this happen when resolving transforms as well? I know there's a check in place to prevent
Yeah it looks redundant to me as well since we already check it when trying to resolve a non-annotation. Was it addressing the |
Yes, without causing any failures. |
…in expressions. Thanks Mariusz Felisiak and Simon Charette for reviews.
c612b1f
to
8b040e3
Compare
By updating
Query.resolve_ref
to return the transform instead of the field, we enableF
objects to include transforms.This resolves ticket-25534 and ticket-31639.