--
Ticket URL: <https://code.djangoproject.com/ticket/17485>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* needs_better_patch: => 0
* needs_docs: => 0
* needs_tests: => 0
* stage: Unreviewed => Accepted
Comment:
You've clearly demonstrated that this is a bug: Django misbehaves badly in
this situation.
It seems to me that `select_related` is the opposite of `defer`. Is there
a use case for using both on the same field? Maybe it's
`.defer('foo').select_related('foo__bar__baz')`, in order to fetch `foo`,
`bar` and `baz` in one query on the first access to `foo`? (Does that
actually work?)
Anyway, I think we should fix the bug and all affected tests with one
patch.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:1>
* owner: nobody => koniiiik
Comment:
Replying to [comment:1 aaugustin]:
> It seems to me that `select_related` is the opposite of `defer`. Is
there a use case for using both on the same field? Maybe it's
`.defer('foo').select_related('foo__bar__baz')`, in order to fetch `foo`,
`bar` and `baz` in one query on the first access to `foo`? (Does that
actually work?)
Nope, that does not work nor do I think there's an easy way to make it, at
least with the current implementation of deferred fields. The whole
example I've given is based on the two failing test cases, I can't imagine
any sane use case at the moment.
I think the test was meant more like something to make sure the ORM
doesn't omit a field on which it makes a join, i. e. issuing a
`select_related` on a deferred field will make the field immediate.
I've come up with a way to fix this, I'll try to implement it later today.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:2>
Comment (by aaugustin):
So shouldn't we forbid using defer and select_related on the same field
entirely, by raising an exception?
It's already broken, so that change wouldn't be too much backwards
incompatible.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:3>
Comment (by koniiiik):
Replying to [comment:3 aaugustin]:
> So shouldn't we forbid using defer and select_related on the same field
entirely, by raising an exception?
The problem is, what should happen if you issue a `select_related` with a
depth and then defer a `ForeignKey` field? Should it raise an exception as
well?
Other than that, I think both routes are about the same in terms of
complexity of solution, either way you have to detect the affected
`ForeignKey`s and react accordingly.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:4>
Comment (by koniiiik):
Okay, took me a little longer but I have an almost working solution to
this with the following behavior: an explicit `defer` combined with
`select_related` on the same field results in an `InvalidQuery`; deferring
a `ForeignKey` and using a depth-based `select_related` results in the
specific relationship being skipped.
Now the problem is, what should happen when someone uses `only` and
`select_related` in a way that the `ForeignKey` would be omitted? Do we
want to issue the exception as well or do we want to automatically add the
field to the set of loaded ones? Currently it behaves the first way,
though it might make sense in the second way as well.
It is used in
`regressiontests.select_related_regress.SelectRelatedRegressTests.test_regression_12851`
this way at the moment – should I go ahead and update the test or do we
want to keep the test case and add more magic?
Personally I'm in favor of the first behavior, i. e. raising an exception,
as it is more explicit. Stating I only want to load other fields and then
asking for a `select_related` on an omitted field seems contradictory to
me but there may be those who disagree.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:5>
* cc: niwi@… (added)
Comment:
Here is the original patch, updated to the latest version of django.
(https://github.com/niwibe/django/tree/ticket_17485)
I do not know the status of this ticket, but the solution seems
reasonable.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:6>
Comment (by akaariai):
I took a look at the patch and it seems good to me. At first I was going
to suggest that select_related() should just override .only() and .defer()
for the related fields, but thinking it more carefully it is more explicit
to disallow this case, and even more importantly there are already two
votes for erroring out in these cases. The sad thing is there is a minor
backwards incompatibility for the .only() case (right?), but I guess that
problem could be dismissed as being a bug fix.
I would like to add an assert to the deferred class factory which prevents
the creation of deferred classes using field.name instead of
field.attname.
It seems there is some need to refactor, or at least comment the
select_related implementation in models/sql/compiler.py and then in
models/query.py. Both the query.py and compiler.py code must produce same
columns and column positions for the select, so there is one possibility
for cleanup. In addition, the compiler.py join logic is very hard to
understand currently, and at least some comments in there is needed.
I tried to do some of the refactoring for this ticket, but soon realised
the minor changes cascade to all around the select_related code. So, for
this ticket the above refactoring should be skipped.
Before moving forward here, any thought on the backwards incompatibility
issue? This would be disallowed by the new code:
{{{
qs.only('name').select_related('related')
}}}
it would raise an error, and instead you would need to do:
{{{
qs.only('name', 'related').select_related('related')
}}}
I doubt there are many users who would see this issue...
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:7>
Comment (by koniiiik):
Actually, I'm not entirely convinced by the patch myself, mainly for
reasons you already mentioned – there is some code duplication in place
which should ideally be refactored. At the moment, however, I don't feel
like attempting to do this kind of refactor, maybe sometime after
composite fields are finished...
As for the backwards compatibility issue, personally I'd prefer being more
explicit. I think the first line is a contradiction – first it says not to
load the field `related` and then it asks for a join on this field. I
don't think this makes much sense and there are two ways of interpreting
this, we can either ignore the request to defer the field or the request
to make a join. Either way the line is ambiguous and for me it makes sense
to require the user to be consistent.
I'm going to take care of my github branch and will make a pull request
later...
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:8>
Comment (by akaariai):
The thing is, your fix is pretty straightforward. Yes, there is room for
larger refactoring, but that refactoring will take a lot of time. It would
be nice to just fix this one issue, so this would not block solving other
issues. Then later on it would be possible to tackle the select_related
total-refactor (TM).
This is why I think we should go with your patch. It isn't perfect, but
solves the immediate issue at hand.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:9>
Comment (by koniiiik):
Took me longer to update my repo than I expected, sorry about that.
Two updates to the patch:
1. I bumped the version number in the docs.
2. The original patch breaks the test case introduced with a fix to
#17876. This test case seems to be broken as it expects the related field
to be pulled in.
Pull request: https://github.com/django/django/pull/105
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:10>
* status: new => closed
* resolution: => fixed
Comment:
In [b6c356b7bb97f3d6d4831b31e67868313bbbc090]:
{{{
#!CommitTicketReference repository=""
revision="b6c356b7bb97f3d6d4831b31e67868313bbbc090"
Fixed #17485 -- Made defer work with select_related
This commit tackles a couple of issues. First, in certain cases there
were some mixups if field.attname or field.name should be deferred.
Field.attname is now always used.
Another issue tackled is a case where field is both deferred by
.only(), and selected by select_related. This case is now an error.
A lot of thanks to koniiiik (Michal Petrucha) for the patch, and
to Andrei Antoukh for review.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:11>
* cc: real.human@… (added)
* status: closed => reopened
* resolution: fixed =>
Comment:
Looks like there's a bug in this commit that was not caught by the
included tests. When I try to use `only()` and `select_related()` and pass
the same fields to both, spanning multiple FKs I get an erroneous
exception telling me that I can't use `select_related` for a deferred
field. That's not what I'm doing. I'm using `select_related` for a field
that is explicitly NOT deferred.
I have written a patch with a new test, but I don't have a fix because I
don't fully understand what the code in this commit is doing.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:12>
* owner: koniiiik => akaariai
* status: reopened => new
* severity: Normal => Release blocker
Comment:
Seems like I have some fixing to do...
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:13>
* stage: Accepted => Ready for checkin
Comment:
A fix for this is available from:
https://github.com/akaariai/django/tree/ticket_17485_fix
The problem was that for deeper nestings of select_related + only the
only() loaded fields were taken for self.query.model instead of the model
the field was located in. This managed to work correctly when
self.query.model was the same model as the field was in.
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:14>
* status: new => closed
* resolution: => fixed
Comment:
In [changeset:"f399a804c9436a5d3ef9e2b73c337904af2c753d"]:
{{{
#!CommitTicketReference repository=""
revision="f399a804c9436a5d3ef9e2b73c337904af2c753d"
Fixed #17485 regression -- only + select_related interaction
When doing deeper than one level select_related() + only queries(), the
code introduced in b6c356b7bb97f3d6d4831b31e67868313bbbc090 errored
incorrectly.
Thanks to mrmachine for report & test case.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/17485#comment:15>