Ticket #14483: spatial_subquery2.diff

File spatial_subquery2.diff, 8.1 KB (added by milosu, 5 years ago)

performance improvement for postgis, that allows postgres to efficiently replace subselect with its result while preparing the query plan

  • django/contrib/gis/db/models/sql/where.py

     
    4646            data, params = lvalue.process(lookup_type, params_or_value, connection)
    4747            alias, col, db_type = data
    4848            spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
     49
     50            tables = [alias]
     51            if hasattr(params, 'as_sql'):
     52                if hasattr(params, 'tables'):
     53                    tables.extend(params.tables)
     54                extra, params = params.as_sql(qn, connection)
     55                spatial_sql = spatial_sql % extra
     56                return spatial_sql, params, tables
    4957            return spatial_sql, params, [alias]
    5058        else:
    5159            return super(GeoWhereNode, self).make_atom(child, qn, connection)
  • django/contrib/gis/db/models/fields.py

     
    11from django.db.models.fields import Field
    22from django.db.models.sql.expressions import SQLEvaluator
     3from django.db.models.query_utils import QueryWrapper
    34from django.utils.translation import ugettext_lazy as _
    45from django.contrib.gis import forms
    56from django.contrib.gis.db.models.proxy import GeometryProxy
     
    148149        """
    149150        if isinstance(value, SQLEvaluator):
    150151            return value
     152        elif hasattr(value, 'query'):
     153            return value
    151154        elif isinstance(value, (tuple, list)):
    152155            geom = value[0]
    153156            seq_value = True
     
    234237                    pass
    235238                else:
    236239                    params += value[1:]
     240            elif hasattr(value, 'as_sql') or hasattr(value, '_as_sql'):
     241                # If the value has a relabel_aliases method, it will need to
     242                # be invoked before the final SQL is evaluated
     243                if hasattr(value, 'relabel_aliases'):
     244                    return value
     245                if hasattr(value, 'as_sql'):
     246                    sql, params = value.as_sql()
     247                else:
     248                    sql, params = value._as_sql(connection=connection)
     249                subselect_sql = connection.ops.wrap_spatial_subselect(sql)
     250                return QueryWrapper((subselect_sql), params, value.query.tables)
    237251            elif isinstance(value, SQLEvaluator):
    238252                params = []
    239253            else:
  • django/contrib/gis/db/backends/spatialite/operations.py

     
    188188        """
    189189        def transform_value(value, srid):
    190190            return not (value is None or value.srid == srid)
     191        if hasattr(value, 'query'):
     192            return '%s'
    191193        if hasattr(value, 'expression'):
    192194            if transform_value(value, f.srid):
    193195                placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
  • django/contrib/gis/db/backends/mysql/operations.py

     
    4141        MySQL does not support spatial transformations, there is no need to
    4242        modify the placeholder based on the contents of the given value.
    4343        """
     44        if hasattr(value, 'query'):
     45            return '%s'
    4446        if hasattr(value, 'expression'):
    4547            placeholder = '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
    4648        else:
  • django/contrib/gis/db/backends/oracle/operations.py

     
    205205        """
    206206        if value is None:
    207207            return 'NULL'
    208 
     208        if hasattr(value, 'query'):
     209            return '%s'
    209210        def transform_value(val, srid):
    210211            return val.srid != srid
    211212
  • django/contrib/gis/db/backends/postgis/operations.py

     
    387387        SRID of the field.  Specifically, this routine will substitute in the
    388388        ST_Transform() function call.
    389389        """
    390         if value is None or value.srid == f.srid:
     390        if value is None or hasattr(value, 'query') or value.srid == f.srid:
    391391            placeholder = '%s'
    392392        else:
    393393            # Adding Transform() to the SQL placeholder.
  • backends/base.py

     
    131131    def spatial_ref_sys(self):
    132132        raise NotImplementedError
    133133
     134    def wrap_spatial_subselect(self, subselect_sql):
     135        return '(%s)' % subselect_sql
     136
    134137class SpatialRefSysMixin(object):
    135138    """
    136139    The SpatialRefSysMixin is a class used by the database-dependent
  • backends/postgis/func.sql

     
     1/* function that will immediately execute subselect that returns spatial geometry */
     2create or replace function execute_spatial_subselect(a_query text)
     3returns geometry as
     4$$
     5  DECLARE poly geometry;
     6  BEGIN
     7    EXECUTE a_query INTO poly;
     8    RETURN poly;
     9  END
     10$$ LANGUAGE 'plpgsql' IMMUTABLE;
  • backends/postgis/operations.py

     
    589589    def spatial_ref_sys(self):
    590590        from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
    591591        return SpatialRefSys
     592
     593    def wrap_spatial_subselect(self, subselect_sql):
     594        # use dollar quoting to prevent problems with quoted params
  • tests/modeltests/spatial_subquery/models.py

    +        return '(execute_spatial_subselect($$%s$$))' % subselect_sql
     
     1"""
     2Tests for spatial subqueries
     3"""
     4
     5from django.contrib.gis.db import models
     6
     7class Address(models.Model):
     8    city = models.CharField(max_length=50)
     9    location = models.PointField(srid=4326)
     10    objects = models.GeoManager()
     11    def __unicode__(self):
     12        return u"%s" % (self.city)
     13
     14class District(models.Model):
     15    name = models.CharField(max_length=50)
     16    area = models.MultiPolygonField(srid=4326)
     17    def __unicode__(self):
     18        return u"%s" % (self.name)
     19
     20__test__ = {'API_TESTS':"""
     21
     22>>> qset = Address.objects.filter(location__within = District.objects.filter(name='Boston area').values('area'))
     23>>> str(qset.query)
     24'SELECT "spatial_subquery_address"."id", "spatial_subquery_address"."city", "spatial_subquery_address"."location" FROM "spatial_subquery_address" WHERE ST_Within("spatial_subquery_address"."location", (execute_spatial_subselect($$SELECT U0."area" FROM "spatial_subquery_district" U0 WHERE U0."name" = Boston area $$)))'
     25
     26>>> list(qset)
     27[]
     28
     29>>> qset.query.tables   # check presence of both tables for johnny cache patch
     30['spatial_subquery_address']
     31
     32>>> qset.query.where.result_tables # also QueryWrapper tables should be present
     33['spatial_subquery_address', 'spatial_subquery_district']
     34
     35"""}
Back to Top