--- a/web/lib/django/contrib/gis/db/models/sql/query.py Wed May 19 17:43:59 2010 +0200
+++ b/web/lib/django/contrib/gis/db/models/sql/query.py Tue May 25 02:43:45 2010 +0200
@@ -1,21 +1,25 @@
-from itertools import izip
+from django.db import connections
from django.db.models.query import sql
-from django.db.models.fields.related import ForeignKey
-from django.contrib.gis.db.backend import SpatialBackend
from django.contrib.gis.db.models.fields import GeometryField
-from django.contrib.gis.db.models.sql import aggregates as gis_aggregates_module
+from django.contrib.gis.db.models.sql import aggregates as gis_aggregates
from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
from django.contrib.gis.db.models.sql.where import GeoWhereNode
+from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Area, Distance
-# Valid GIS query types.
-ALL_TERMS = sql.constants.QUERY_TERMS.copy()
-ALL_TERMS.update(SpatialBackend.gis_terms)
-# Pulling out other needed constants/routines to avoid attribute lookups.
-TABLE_NAME = sql.constants.TABLE_NAME
-get_proxied_model = sql.query.get_proxied_model
+ALL_TERMS = dict([(x, None) for x in (
+ 'bbcontains', 'bboverlaps', 'contained', 'contains',
+ 'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint',
+ 'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte',
+ 'dwithin', 'equals', 'exact',
+ 'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within',
+ 'left', 'right', 'overlaps_left', 'overlaps_right',
+ 'overlaps_above', 'overlaps_below',
+ 'strictly_above', 'strictly_below'
+ )])
+ALL_TERMS.update(sql.constants.QUERY_TERMS)
class GeoQuery(sql.Query):
"""
@@ -23,11 +27,13 @@
"""
# Overridding the valid query terms.
query_terms = ALL_TERMS
- aggregates_module = gis_aggregates_module
+ aggregates_module = gis_aggregates
+
+ compiler = 'GeoSQLCompiler'
#### Methods overridden from the base Query class ####
- def __init__(self, model, conn):
- super(GeoQuery, self).__init__(model, conn, where=GeoWhereNode)
+ def __init__(self, model, where=GeoWhereNode):
+ super(GeoQuery, self).__init__(model, where)
# The following attributes are customized for the GeoQuerySet.
# The GeoWhereNode and SpatialBackend classes contain backend-specific
# routines and functions.
@@ -35,13 +41,6 @@
self.transformed_srid = None
self.extra_select_fields = {}
- if SpatialBackend.oracle:
- # Have to override this so that GeoQuery, instead of OracleQuery,
- # is returned when unpickling.
- def __reduce__(self):
- callable, args, data = super(GeoQuery, self).__reduce__()
- return (unpickle_geoquery, (), data)
-
def clone(self, *args, **kwargs):
obj = super(GeoQuery, self).clone(*args, **kwargs)
# Customized selection dictionary and transformed srid flag have
@@ -51,286 +50,57 @@
obj.extra_select_fields = self.extra_select_fields.copy()
return obj
- def get_columns(self, with_aliases=False):
- """
- Return the list of columns to use in the select statement. If no
- columns have been specified, returns all columns relating to fields in
- the model.
-
- If 'with_aliases' is true, any column names that are duplicated
- (without the table names) are given unique aliases. This is needed in
- some cases to avoid ambiguitity with nested queries.
-
- This routine is overridden from Query to handle customized selection of
- geometry columns.
- """
- qn = self.quote_name_unless_alias
- qn2 = self.connection.ops.quote_name
- result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias))
- for alias, col in self.extra_select.iteritems()]
- aliases = set(self.extra_select.keys())
- if with_aliases:
- col_aliases = aliases.copy()
- else:
- col_aliases = set()
- if self.select:
- only_load = self.deferred_to_columns()
- # This loop customized for GeoQuery.
- for col, field in izip(self.select, self.select_fields):
- if isinstance(col, (list, tuple)):
- alias, column = col
- table = self.alias_map[alias][TABLE_NAME]
- if table in only_load and col not in only_load[table]:
- continue
- r = self.get_field_select(field, alias, column)
- if with_aliases:
- if col[1] in col_aliases:
- c_alias = 'Col%d' % len(col_aliases)
- result.append('%s AS %s' % (r, c_alias))
- aliases.add(c_alias)
- col_aliases.add(c_alias)
- else:
- result.append('%s AS %s' % (r, qn2(col[1])))
- aliases.add(r)
- col_aliases.add(col[1])
- else:
- result.append(r)
- aliases.add(r)
- col_aliases.add(col[1])
- else:
- result.append(col.as_sql(quote_func=qn))
-
- if hasattr(col, 'alias'):
- aliases.add(col.alias)
- col_aliases.add(col.alias)
-
- elif self.default_cols:
- cols, new_aliases = self.get_default_columns(with_aliases,
- col_aliases)
- result.extend(cols)
- aliases.update(new_aliases)
-
- result.extend([
- '%s%s' % (
- self.get_extra_select_format(alias) % aggregate.as_sql(quote_func=qn),
- alias is not None and ' AS %s' % alias or ''
- )
- for alias, aggregate in self.aggregate_select.items()
- ])
-
- # This loop customized for GeoQuery.
- for (table, col), field in izip(self.related_select_cols, self.related_select_fields):
- r = self.get_field_select(field, table, col)
- if with_aliases and col in col_aliases:
- c_alias = 'Col%d' % len(col_aliases)
- result.append('%s AS %s' % (r, c_alias))
- aliases.add(c_alias)
- col_aliases.add(c_alias)
- else:
- result.append(r)
- aliases.add(r)
- col_aliases.add(col)
-
- self._select_aliases = aliases
- return result
-
- def get_default_columns(self, with_aliases=False, col_aliases=None,
- start_alias=None, opts=None, as_pairs=False):
- """
- Computes the default columns for selecting every field in the base
- model. Will sometimes be called to pull in related models (e.g. via
- select_related), in which case "opts" and "start_alias" will be given
- to provide a starting point for the traversal.
-
- Returns a list of strings, quoted appropriately for use in SQL
- directly, as well as a set of aliases used in the select statement (if
- 'as_pairs' is True, returns a list of (alias, col_name) pairs instead
- of strings as the first component and None as the second component).
-
- This routine is overridden from Query to handle customized selection of
- geometry columns.
- """
- result = []
- if opts is None:
- opts = self.model._meta
- aliases = set()
- only_load = self.deferred_to_columns()
- # Skip all proxy to the root proxied model
- proxied_model = get_proxied_model(opts)
-
- if start_alias:
- seen = {None: start_alias}
- for field, model in opts.get_fields_with_model():
- if start_alias:
- try:
- alias = seen[model]
- except KeyError:
- if model is proxied_model:
- alias = start_alias
- else:
- link_field = opts.get_ancestor_link(model)
- alias = self.join((start_alias, model._meta.db_table,
- link_field.column, model._meta.pk.column))
- seen[model] = alias
- else:
- # If we're starting from the base model of the queryset, the
- # aliases will have already been set up in pre_sql_setup(), so
- # we can save time here.
- alias = self.included_inherited_models[model]
- table = self.alias_map[alias][TABLE_NAME]
- if table in only_load and field.column not in only_load[table]:
- continue
- if as_pairs:
- result.append((alias, field.column))
- aliases.add(alias)
- continue
- # This part of the function is customized for GeoQuery. We
- # see if there was any custom selection specified in the
- # dictionary, and set up the selection format appropriately.
- field_sel = self.get_field_select(field, alias)
- if with_aliases and field.column in col_aliases:
- c_alias = 'Col%d' % len(col_aliases)
- result.append('%s AS %s' % (field_sel, c_alias))
- col_aliases.add(c_alias)
- aliases.add(c_alias)
- else:
- r = field_sel
- result.append(r)
- aliases.add(r)
- if with_aliases:
- col_aliases.add(field.column)
- return result, aliases
-
- def resolve_columns(self, row, fields=()):
- """
- This routine is necessary so that distances and geometries returned
- from extra selection SQL get resolved appropriately into Python
- objects.
- """
- values = []
- aliases = self.extra_select.keys()
- if self.aggregates:
- # If we have an aggregate annotation, must extend the aliases
- # so their corresponding row values are included.
- aliases.extend([None for i in xrange(len(self.aggregates))])
-
- # Have to set a starting row number offset that is used for
- # determining the correct starting row index -- needed for
- # doing pagination with Oracle.
- rn_offset = 0
- if SpatialBackend.oracle:
- if self.high_mark is not None or self.low_mark: rn_offset = 1
- index_start = rn_offset + len(aliases)
-
- # Converting any extra selection values (e.g., geometries and
- # distance objects added by GeoQuerySet methods).
- values = [self.convert_values(v, self.extra_select_fields.get(a, None))
- for v, a in izip(row[rn_offset:index_start], aliases)]
- if SpatialBackend.oracle or getattr(self, 'geo_values', False):
- # We resolve the rest of the columns if we're on Oracle or if
- # the `geo_values` attribute is defined.
- for value, field in izip(row[index_start:], fields):
- values.append(self.convert_values(value, field))
- else:
- values.extend(row[index_start:])
- return tuple(values)
-
- def convert_values(self, value, field):
+ def convert_values(self, value, field, connection):
"""
Using the same routines that Oracle does we can convert our
extra selection objects into Geometry and Distance objects.
TODO: Make converted objects 'lazy' for less overhead.
"""
- if SpatialBackend.oracle:
+ if connection.ops.oracle:
# Running through Oracle's first.
- value = super(GeoQuery, self).convert_values(value, field or GeomField())
+ value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
- if isinstance(field, DistanceField):
+ if value is None:
+ # Output from spatial function is NULL (e.g., called
+ # function on a geometry field with NULL value).
+ pass
+ elif isinstance(field, DistanceField):
# Using the field's distance attribute, can instantiate
# `Distance` with the right context.
value = Distance(**{field.distance_att : value})
elif isinstance(field, AreaField):
value = Area(**{field.area_att : value})
elif isinstance(field, (GeomField, GeometryField)) and value:
- value = SpatialBackend.Geometry(value)
+ value = Geometry(value)
return value
- def resolve_aggregate(self, value, aggregate):
+ def get_aggregation(self, using):
+ # Remove any aggregates marked for reduction from the subquery
+ # and move them to the outer AggregateQuery.
+ connection = connections[using]
+ for alias, aggregate in self.aggregate_select.items():
+ if isinstance(aggregate, gis_aggregates.GeoAggregate):
+ if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
+ self.extra_select_fields[alias] = GeomField()
+ return super(GeoQuery, self).get_aggregation(using)
+
+ def resolve_aggregate(self, value, aggregate, connection):
"""
Overridden from GeoQuery's normalize to handle the conversion of
GeoAggregate objects.
"""
if isinstance(aggregate, self.aggregates_module.GeoAggregate):
if aggregate.is_extent:
- return self.aggregates_module.convert_extent(value)
+ if aggregate.is_extent == '3D':
+ return connection.ops.convert_extent3d(value)
+ else:
+ return connection.ops.convert_extent(value)
else:
- return self.aggregates_module.convert_geom(value, aggregate.source)
- else:
- return super(GeoQuery, self).resolve_aggregate(value, aggregate)
-
- #### Routines unique to GeoQuery ####
- def get_extra_select_format(self, alias):
- sel_fmt = '%s'
- if alias in self.custom_select:
- sel_fmt = sel_fmt % self.custom_select[alias]
- return sel_fmt
-
- def get_field_select(self, field, alias=None, column=None):
- """
- Returns the SELECT SQL string for the given field. Figures out
- if any custom selection SQL is needed for the column The `alias`
- keyword may be used to manually specify the database table where
- the column exists, if not in the model associated with this
- `GeoQuery`. Similarly, `column` may be used to specify the exact
- column name, rather than using the `column` attribute on `field`.
- """
- sel_fmt = self.get_select_format(field)
- if field in self.custom_select:
- field_sel = sel_fmt % self.custom_select[field]
+ return connection.ops.convert_geom(value, aggregate.source)
else:
- field_sel = sel_fmt % self._field_column(field, alias, column)
- return field_sel
-
- def get_select_format(self, fld):
- """
- Returns the selection format string, depending on the requirements
- of the spatial backend. For example, Oracle and MySQL require custom
- selection formats in order to retrieve geometries in OGC WKT. For all
- other fields a simple '%s' format string is returned.
- """
- if SpatialBackend.select and hasattr(fld, 'geom_type'):
- # This allows operations to be done on fields in the SELECT,
- # overriding their values -- used by the Oracle and MySQL
- # spatial backends to get database values as WKT, and by the
- # `transform` method.
- sel_fmt = SpatialBackend.select
-
- # Because WKT doesn't contain spatial reference information,
- # the SRID is prefixed to the returned WKT to ensure that the
- # transformed geometries have an SRID different than that of the
- # field -- this is only used by `transform` for Oracle and
- # SpatiaLite backends.
- if self.transformed_srid and ( SpatialBackend.oracle or
- SpatialBackend.spatialite ):
- sel_fmt = "'SRID=%d;'||%s" % (self.transformed_srid, sel_fmt)
- else:
- sel_fmt = '%s'
- return sel_fmt
+ return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
# Private API utilities, subject to change.
- def _field_column(self, field, table_alias=None, column=None):
- """
- Helper function that returns the database column for the given field.
- The table and column are returned (quoted) in the proper format, e.g.,
- `"geoapp_city"."point"`. If `table_alias` is not specified, the
- database table associated with the model of this `GeoQuery` will be
- used. If `column` is specified, it will be used instead of the value
- in `field.column`.
- """
- if table_alias is None: table_alias = self.model._meta.db_table
- return "%s.%s" % (self.quote_name_unless_alias(table_alias),
- self.connection.ops.quote_name(column or field.column))
-
def _geo_field(self, field_name=None):
"""
Returns the first Geometry field encountered; or specified via the
@@ -347,12 +117,3 @@
# Otherwise, check by the given field name -- which may be
# a lookup to a _related_ geographic field.
return GeoWhereNode._check_geo_field(self.model._meta, field_name)
-
-if SpatialBackend.oracle:
- def unpickle_geoquery():
- """
- Utility function, called by Python's unpickling machinery, that handles
- unpickling of GeoQuery subclasses of OracleQuery.
- """
- return GeoQuery.__new__(GeoQuery)
- unpickle_geoquery.__safe_for_unpickling__ = True