diff -r b758351d191f -r cc9b7e14412b web/lib/django/contrib/gis/db/models/sql/where.py --- a/web/lib/django/contrib/gis/db/models/sql/where.py Wed May 19 17:43:59 2010 +0200 +++ b/web/lib/django/contrib/gis/db/models/sql/where.py Tue May 25 02:43:45 2010 +0200 @@ -1,23 +1,31 @@ -from django.db import connection from django.db.models.fields import Field, FieldDoesNotExist from django.db.models.sql.constants import LOOKUP_SEP from django.db.models.sql.expressions import SQLEvaluator -from django.db.models.sql.where import WhereNode -from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend +from django.db.models.sql.where import Constraint, WhereNode from django.contrib.gis.db.models.fields import GeometryField -qn = connection.ops.quote_name -class GeoAnnotation(object): +class GeoConstraint(Constraint): + """ + This subclass overrides `process` to better handle geographic SQL + construction. """ - The annotation used for GeometryFields; basically a placeholder - for metadata needed by the `get_geo_where_clause` of the spatial - backend. - """ - def __init__(self, field, value, where): - self.geodetic = field.geodetic - self.geom_type = field.geom_type - self.value = value - self.where = tuple(where) + def __init__(self, init_constraint): + self.alias = init_constraint.alias + self.col = init_constraint.col + self.field = init_constraint.field + + def process(self, lookup_type, value, connection): + if isinstance(value, SQLEvaluator): + # Make sure the F Expression destination field exists, and + # set an `srid` attribute with the same as that of the + # destination. + geo_fld = GeoWhereNode._check_geo_field(value.opts, value.expression.name) + if not geo_fld: + raise ValueError('No geographic field found in expression.') + value.srid = geo_fld.srid + db_type = self.field.db_type(connection=connection) + params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection) + return (self.alias, self.col, db_type), params class GeoWhereNode(WhereNode): """ @@ -25,81 +33,26 @@ these are tied to the GeoQuery class that created it. """ def add(self, data, connector): - """ - This is overridden from the regular WhereNode to handle the - peculiarties of GeometryFields, because they need a special - annotation object that contains the spatial metadata from the - field to generate the spatial SQL. - """ - if not isinstance(data, (list, tuple)): - return super(WhereNode, self).add(data, connector) - - obj, lookup_type, value = data - col, field = obj.col, obj.field - - if not hasattr(field, "geom_type"): - # Not a geographic field, so call `WhereNode.add`. - return super(GeoWhereNode, self).add(data, connector) - else: - if isinstance(value, SQLEvaluator): - # Getting the geographic field to compare with from the expression. - geo_fld = self._check_geo_field(value.opts, value.expression.name) - if not geo_fld: - raise ValueError('No geographic field found in expression.') - - # Get the SRID of the geometry field that the expression was meant - # to operate on -- it's needed to determine whether transformation - # SQL is necessary. - srid = geo_fld.srid - - # Getting the quoted representation of the geometry column that - # the expression is operating on. - geo_col = '%s.%s' % tuple(map(qn, value.cols[value.expression])) + if isinstance(data, (list, tuple)): + obj, lookup_type, value = data + if ( isinstance(obj, Constraint) and + isinstance(obj.field, GeometryField) ): + data = (GeoConstraint(obj), lookup_type, value) + super(GeoWhereNode, self).add(data, connector) - # If it's in a different SRID, we'll need to wrap in - # transformation SQL. - if not srid is None and srid != field.srid and SpatialBackend.transform: - placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field.srid) - else: - placeholder = '%s' - - # Setting these up as if we had called `field.get_db_prep_lookup()`. - where = [placeholder % geo_col] - params = () - else: - # `GeometryField.get_db_prep_lookup` returns a where clause - # substitution array in addition to the parameters. - where, params = field.get_db_prep_lookup(lookup_type, value) - - # The annotation will be a `GeoAnnotation` object that - # will contain the necessary geometry field metadata for - # the `get_geo_where_clause` to construct the appropriate - # spatial SQL when `make_atom` is called. - annotation = GeoAnnotation(field, value, where) - return super(WhereNode, self).add(((obj.alias, col, field.db_type()), lookup_type, annotation, params), connector) - - def make_atom(self, child, qn): - obj, lookup_type, value_annot, params = child - - if isinstance(value_annot, GeoAnnotation): - if lookup_type in SpatialBackend.gis_terms: - # Getting the geographic where clause; substitution parameters - # will be populated in the GeoFieldSQL object returned by the - # GeometryField. - alias, col, db_type = obj - gwc = get_geo_where_clause(alias, col, lookup_type, value_annot) - return gwc % value_annot.where, params - else: - raise TypeError('Invalid lookup type: %r' % lookup_type) + def make_atom(self, child, qn, connection): + lvalue, lookup_type, value_annot, params_or_value = child + if isinstance(lvalue, GeoConstraint): + data, params = lvalue.process(lookup_type, params_or_value, connection) + spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn) + return spatial_sql, params else: - # If not a GeometryField, call the `make_atom` from the - # base class. - return super(GeoWhereNode, self).make_atom(child, qn) + return super(GeoWhereNode, self).make_atom(child, qn, connection) @classmethod def _check_geo_field(cls, opts, lookup): """ - Utility for checking the given lookup with the given model options. + Utility for checking the given lookup with the given model options. The lookup is a string either specifying the geographic field, e.g. 'point, 'the_geom', or a related lookup on a geographic field like 'address__point'. @@ -121,7 +74,7 @@ # If the field list is still around, then it means that the # lookup was for a geometry field across a relationship -- # thus we keep on getting the related model options and the - # model field associated with the next field in the list + # model field associated with the next field in the list # until there's no more left. while len(field_list): opts = geo_fld.rel.to._meta