Opened 4 years ago

Last modified 7 months ago

#31714 new Cleanup/optimization

ResolvedOuterRef object has no get_lookup

Reported by: BorisZZZ Owned by:
Component: Database layer (models, ORM) Version: 3.0
Severity: Normal Keywords:
Cc: Denis Verbin Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

This looks like an issue very similar to #28621, only in this case the issue is cause by using the annotation on the LHS of a filter.
For example,

class A(models.Model):
    r = IntegerRangeField()
      
A.objects.annotate(x=OuterRef("x")).filter(x__contained_in=F("r"))

will result in the following stack trace:

Traceback (most recent call last):
  ...
  File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 904, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 923, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1350, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1377, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1267, in build_filter
    condition = self.build_lookup(lookups, reffed_expression, value)
  File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1153, in build_lookup
    lookup_class = lhs.get_lookup(lookup_name)
AttributeError: 'ResolvedOuterRef' object has no attribute 'get_lookup'

Change History (7)

comment:1 by Simon Charette, 4 years ago

Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

I think we could error out which a more appropriate message but it will be very hard to make the above queryset work since OuterRef cannot be resolved to a proper output_field until its queryset gets annotated to an outer query. I guess we could allow an output_field to be specified on OuterRef initialization to allow filter to reference it before it's resolved to a Col.

comment:2 by Denis Verbin, 3 years ago

Cc: Denis Verbin added

I'm faced with the same issue (#32043) and hope it will be fixed.

comment:3 by Phil-Barber, 2 years ago

Owner: changed from nobody to Phil-Barber
Status: newassigned

comment:4 by Mariusz Felisiak, 2 years ago

As a workaround OuterRef can be wrapped in ExpressionWrapper, e.g.

A.objects.annotate(
    x=ExpressionWrapper(OuterRef("x"), output_field=IntegerRangeField())
).filter(x__contained_in=F("r"))

comment:5 by Mariusz Felisiak, 19 months ago

filtered_relation.tests.FilteredRelationTests.test_conditional_expression_with_subquery should work when this ticket is fixed:

def test_conditional_expression_with_subquery(self):
    self.assertSequenceEqual(
        Author.objects.annotate(
            book_editor=FilteredRelation(
                "book__editor",
                condition=Q(Exists(
                    Editor.objects.annotate(
                        author_name=OuterRef('name'),
                    ).filter(
                        author_name__istartswith=F('name'),
                    )
                )),
            ),
        ),
        [self.author1],
    )

comment:6 by Mariusz Felisiak, 19 months ago

Owner: Phil-Barber removed
Status: assignednew

comment:7 by Simon Charette, 7 months ago

FWIW I stumbled upon another user running into the same problem but for _output_field_or_none.

I think that an appropriate solution here could be to

  1. Allow an output_field to be specified to OuterRef and percolate to ResolvedOuterRef. It's a common pattern for Expression-like to accept this kind of parameter.
  2. At the time ResolvedOuterRef performs its final resolution in the outer query error out if there is a mismatch of output_field (type only should be enough) between what was explicitly specified and the resolved expression one.

While the ExpressionWrapper solution works I think it's a big leap for users to figure out they should be using it when they encounter a seemingly random AttributeError.

Note: See TracTickets for help on using tickets.
Back to Top