#30687: GIS distance lookups fail within subqueries using OuterRef
-------------------------------------+-------------------------------------
               Reporter:  Andrew     |          Owner:  nobody
  Brown                              |
                   Type:  Bug        |         Status:  new
              Component:  Database   |        Version:  2.2
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 I discovered this when trying to make a query of this form:

 {{{#!python
 from django.db import models
 from django.contrib.gis.db.models.fields import PointField, RasterField

 class ModelA(models.Model):
     pointA = PointField()

 class ModelB(models.Model):
     pointB = PointField()


 def query1():
     return ModelA.objects.annotate(
         has_value=Exists(ModelB.objects.filter(
             pointB__dwithin=(OuterRef("pointA"), 10)
         ))
     ).filter(has_value=True)
 }}}

 This fails with "ValueError: This queryset contains a reference to an
 outer query and may only be used in a subquery"

 I dug into things, and while I may not fully understand how queries are
 processed, I think I've identified the problem in
 `Query.resolve_lookup_value()`:

 {{{#!python
 def resolve_lookup_value(self, value, can_reuse, allow_joins, simple_col):
     if hasattr(value, 'resolve_expression'):
         kwargs = {'reuse': can_reuse, 'allow_joins': allow_joins}
         if isinstance(value, F):
             kwargs['simple_col'] = simple_col
         value = value.resolve_expression(self, **kwargs)
     elif isinstance(value, (list, tuple)):
         # The items of the iterable may be expressions and therefore need
         # to be resolved independently.
         for sub_value in value:
             if hasattr(sub_value, 'resolve_expression'):
                 if isinstance(sub_value, F):
                     sub_value.resolve_expression(
                         self, reuse=can_reuse, allow_joins=allow_joins,
                         simple_col=simple_col,
                     )
                 else:
                     sub_value.resolve_expression(self, reuse=can_reuse,
 allow_joins=allow_joins)
     return value
 }}}

 This resolves the value passed in as the rhs of a filter. For single
 objects, it calls `resolve_expression()` and then returns the result. But
 for multiple objects in a list or tuple, it calls `resolve_expression()`
 on each but doesn't return the resolved objects. Rather it returns the
 original list.

 As a consequence, during the call to filter, the passed in `OuterRef`
 object doesn't get resolved to a `ResolvedOuterRef` object, since the
 `__dwithin` lookup takes a tuple of `(value, distance)`, triggering that
 second code path above. Later during the call to annotate, the `OuterRef`
 does resolve to a `ResolvedOuterRef` but when the query is compiled,
 `ResolvedOuterRef.as_sql()` is called which raises the error.

 I modified `resolve_lookup_value()` to return the list or tuple of
 resolved values, and wrote a test for the query above, which I will submit
 in a PR shortly.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/30687>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/050.6521171f7af93695fe73c74dbb0b5fdd%40djangoproject.com.

Reply via email to