Permalink
Browse files

[soc2009/multidb] Fixed #11741 -- Updates to the spatial backends (e.…

…g., re-enabled POSTGIS_VERSION setting); added geometry backend module. Patch from Justin Bronn.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11872 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 11c00d6 commit 05b4d2f67bc3e75d2d1aada844d6031cc126e48e @alex alex committed Dec 16, 2009
@@ -1,5 +1,6 @@
"""
-
+Base/mixin classes for the spatial backend database operations and the
+`SpatialRefSys` model the backend.
"""
import re
from django.conf import settings
@@ -14,8 +15,9 @@ class BaseSpatialOperations(object):
distance_functions = {}
geometry_functions = {}
geometry_operators = {}
+ geography_operators = {}
+ geography_functions = {}
gis_terms = {}
- limited_where = {}
# Quick booleans for the type of this spatial backend, and
# an attribute for the spatial database version tuple (if applicable)
@@ -28,6 +30,9 @@ class BaseSpatialOperations(object):
# How the geometry column should be selected.
select = None
+ # Does the spatial database have a geography type?
+ geography = False
+
area = False
centroid = False
difference = False
@@ -37,11 +42,13 @@ class BaseSpatialOperations(object):
envelope = False
force_rhr = False
mem_size = False
+ bounding_circle = False
num_geom = False
num_points = False
perimeter = False
perimeter3d = False
point_on_surface = False
+ polygonize = False
scale = False
snap_to_grid = False
sym_difference = False
@@ -67,11 +74,6 @@ class BaseSpatialOperations(object):
from_text = False
from_wkb = False
- def geo_quote_name(self, name):
- if isinstance(name, unicode):
- name = name.encode('ascii')
- return "'%s'" % name
-
# Default conversion functions for aggregates; will be overridden if implemented
# for the spatial backend.
def convert_extent(self, box):
@@ -83,6 +85,37 @@ def convert_extent3d(self, box):
def convert_geom(self, geom_val, geom_field):
raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
+ # For quoting column values, rather than columns.
+ def geo_quote_name(self, name):
+ if isinstance(name, unicode):
+ name = name.encode('ascii')
+ return "'%s'" % name
+
+ # GeometryField operations
+ def geo_db_type(self, f):
+ """
+ Returns the database column type for the geometry field on
+ the spatial backend.
+ """
+ raise NotImplementedError
+
+ def get_distance(self, f, value, lookup_type):
+ """
+ Returns the distance parameters for the given geometry field,
+ lookup value, and lookup type.
+ """
+ raise NotImplementedError('Distance operations not available on this spatial backend.')
+
+ def get_geom_placeholder(self, f, value):
+ """
+ Returns the placeholder for the given geometry field with the given
+ value. Depending on the spatial backend, the placeholder may contain a
+ stored procedure call to the transformation function of the spatial
+ backend.
+ """
+ raise NotImplementedError
+
+ # Spatial SQL Construction
def spatial_aggregate_sql(self, agg):
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
@@ -31,6 +31,9 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']])
+ def geo_db_type(self, f):
+ return f.geom_type
+
def get_geom_placeholder(self, value, srid):
"""
The placeholder here has to include MySQL's WKT constructor. Because
@@ -43,8 +46,7 @@ def get_geom_placeholder(self, value, srid):
placeholder = '%s(%%s)' % self.from_text
return placeholder
- def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
- qn = self.quote_name
+ def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
alias, col, db_type = lvalue
geo_col = '%s.%s' % (qn(alias), qn(col))
@@ -7,4 +7,4 @@ class DatabaseWrapper(OracleDatabaseWrapper):
def __init__(self, *args, **kwargs):
super(DatabaseWrapper, self).__init__(*args, **kwargs)
self.creation = OracleCreation(self)
- self.ops = OracleOperations()
+ self.ops = OracleOperations(self)
@@ -7,7 +7,29 @@ class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
pass
class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
- pass
+ def placeholder(self, field, val):
+ if field is None:
+ # A field value of None means the value is raw.
+ return val
+ elif hasattr(field, 'get_placeholder'):
+ # Some fields (e.g. geo fields) need special munging before
+ # they can be inserted.
+ ph = field.get_placeholder(val, self.connection)
+ if ph == 'NULL':
+ # If the placeholder returned is 'NULL', then we need to
+ # to remove None from the Query parameters. Specifically,
+ # cx_Oracle will assume a CHAR type when a placeholder ('%s')
+ # is used for columns of MDSYS.SDO_GEOMETRY. Thus, we use
+ # 'NULL' for the value, and remove None from the query params.
+ # See also #10888.
+ param_idx = self.query.columns.index(field.column)
+ params = list(self.query.params)
+ params.pop(param_idx)
+ self.query.params = tuple(params)
+ return ph
+ else:
+ # Return the common case for the placeholder
+ return '%s'
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
pass
@@ -14,7 +14,7 @@
from django.contrib.gis.db.backends.base import BaseSpatialOperations
from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
from django.contrib.gis.db.backends.util import SpatialFunction
-from django.contrib.gis.geometry import Geometry
+from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Distance
class SDOOperation(SpatialFunction):
@@ -91,7 +91,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
sym_difference = 'SDO_GEOM.SDO_XOR'
transform = 'SDO_CS.TRANSFORM'
union = 'SDO_GEOM.SDO_UNION'
- unionagg = 'SDO_AGGR_UNION'
+ unionagg = 'SDO_AGGR_UNION'
# We want to get SDO Geometries as WKT because it is much easier to
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
@@ -128,6 +128,10 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
gis_terms += geometry_functions.keys()
gis_terms = dict([(term, None) for term in gis_terms])
+ def __init__(self, connection):
+ super(OracleOperations, self).__init__()
+ self.connection = connection
+
def convert_extent(self, clob):
if clob:
# Generally, Oracle returns a polygon for the extent -- however,
@@ -156,7 +160,40 @@ def convert_geom(self, clob, geo_field):
else:
return None
- def get_geom_placeholder(self, value, srid):
+ def geo_db_type(self, f):
+ """
+ Returns the geometry database type for Oracle. Unlike other spatial
+ backends, no stored procedure is necessary and it's the same for all
+ geometry types.
+ """
+ return 'MDSYS.SDO_GEOMETRY'
+
+ def get_distance(self, f, value, lookup_type):
+ """
+ Returns the distance parameters given the value and the lookup type.
+ On Oracle, geometry columns with a geodetic coordinate system behave
+ implicitly like a geography column, and thus meters will be used as
+ the distance parameter on them.
+ """
+ if not value:
+ return []
+ value = value[0]
+ if isinstance(value, Distance):
+ if f.geodetic(self.connection):
+ dist_param = value.m
+ else:
+ dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
+ else:
+ dist_param = value
+
+ # dwithin lookups on oracle require a special string parameter
+ # that starts with "distance=".
+ if lookup_type == 'dwithin':
+ dist_param = 'distance=%s' % dist_param
+
+ return [dist_param]
+
+ def get_geom_placeholder(self, f, value):
"""
Provides a proper substitution value for Geometries that are not in the
SRID of the field. Specifically, this routine will substitute in the
@@ -165,26 +202,25 @@ def get_geom_placeholder(self, value, srid):
if value is None:
return 'NULL'
- def transform_value(value, srid):
- return value.srid != srid
+ def transform_value(val, srid):
+ return val.srid != srid
if hasattr(value, 'expression'):
- if transform_value(value, srid):
- placeholder = '%s(%%s, %s)' % (self.transform, srid)
+ if transform_value(value, f.srid):
+ placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
else:
placeholder = '%s'
# No geometry value used for F expression, substitue in
# the column name instead.
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
else:
- if transform_value(value, srid):
- return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, srid)
+ if transform_value(value, f.srid):
+ return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)
else:
- return 'SDO_GEOMETRY(%%s, %s)' % srid
+ return 'SDO_GEOMETRY(%%s, %s)' % f.srid
- def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
+ def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
- qn = self.quote_name
alias, col, db_type = lvalue
# Getting the quoted table name as `geo_col`.
@@ -214,15 +250,15 @@ def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
if lookup_type == 'relate':
# The SDORelate class handles construction for these queries,
# and verifies the mask argument.
- return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
+ return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(field, geom))
else:
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
- return sdo_op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
+ return sdo_op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
else:
# Lookup info is a SDOOperation instance, whose `as_sql` method returns
# the SQL necessary for the geometry function call. For example:
# SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
- return lookup_info.as_sql(geo_col, self.get_geom_placeholder(value, field.srid))
+ return lookup_info.as_sql(geo_col, self.get_geom_placeholder(field, value))
elif lookup_type == 'isnull':
# Handling 'isnull' lookup type
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
@@ -16,32 +16,43 @@ def sql_indexes_for_field(self, model, f, style):
qn = self.connection.ops.quote_name
db_table = model._meta.db_table
- output.append(style.SQL_KEYWORD('SELECT ') +
- style.SQL_TABLE('AddGeometryColumn') + '(' +
- style.SQL_TABLE(gqn(db_table)) + ', ' +
- style.SQL_FIELD(gqn(f.column)) + ', ' +
- style.SQL_FIELD(str(f.srid)) + ', ' +
- style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
- style.SQL_KEYWORD(str(f.dim)) + ');')
-
- if not f.null:
- # Add a NOT NULL constraint to the field
- output.append(style.SQL_KEYWORD('ALTER TABLE ') +
- style.SQL_TABLE(qn(db_table)) +
- style.SQL_KEYWORD(' ALTER ') +
- style.SQL_FIELD(qn(f.column)) +
- style.SQL_KEYWORD(' SET NOT NULL') + ';')
+ if f.geography:
+ # Geogrophy columns are created normally.
+ pass
+ else:
+ # Geometry columns are created by `AddGeometryColumn`
+ # stored procedure.
+ output.append(style.SQL_KEYWORD('SELECT ') +
+ style.SQL_TABLE('AddGeometryColumn') + '(' +
+ style.SQL_TABLE(gqn(db_table)) + ', ' +
+ style.SQL_FIELD(gqn(f.column)) + ', ' +
+ style.SQL_FIELD(str(f.srid)) + ', ' +
+ style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
+ style.SQL_KEYWORD(str(f.dim)) + ');')
+
+ if not f.null:
+ # Add a NOT NULL constraint to the field
+ output.append(style.SQL_KEYWORD('ALTER TABLE ') +
+ style.SQL_TABLE(qn(db_table)) +
+ style.SQL_KEYWORD(' ALTER ') +
+ style.SQL_FIELD(qn(f.column)) +
+ style.SQL_KEYWORD(' SET NOT NULL') + ';')
if f.spatial_index:
+ # Spatial indexes created the same way for both Geometry and
+ # Geography columns
+ if f.geography:
+ index_opts = ''
+ else:
+ index_opts = ' ' + style.SQL_KEYWORD(self.geom_index_opts)
output.append(style.SQL_KEYWORD('CREATE INDEX ') +
style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
style.SQL_KEYWORD(' ON ') +
style.SQL_TABLE(qn(db_table)) +
style.SQL_KEYWORD(' USING ') +
style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
- style.SQL_FIELD(qn(f.column)) + ' ' +
- style.SQL_KEYWORD(self.geom_index_opts) + ' );')
+ style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
return output
def sql_table_creation_suffix(self):
Oops, something went wrong.

0 comments on commit 05b4d2f

Please sign in to comment.