~ubuntu-branches/ubuntu/jaunty/python-django/jaunty

« back to all changes in this revision

Viewing changes to django/contrib/gis/db/backend/oracle/query.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
 This module contains the spatial lookup types, and the `get_geo_where_clause`
 
3
 routine for Oracle Spatial.
 
4
 
 
5
 Please note that WKT support is broken on the XE version, and thus
 
6
 this backend will not work on such platforms.  Specifically, XE lacks 
 
7
 support for an internal JVM, and Java libraries are required to use 
 
8
 the WKT constructors.
 
9
"""
 
10
import re
 
11
from decimal import Decimal
 
12
from django.db import connection
 
13
from django.contrib.gis.db.backend.util import SpatialFunction
 
14
from django.contrib.gis.measure import Distance
 
15
qn = connection.ops.quote_name
 
16
 
 
17
# The GML, distance, transform, and union procedures.
 
18
AREA = 'SDO_GEOM.SDO_AREA'
 
19
ASGML = 'SDO_UTIL.TO_GMLGEOMETRY'
 
20
CENTROID = 'SDO_GEOM.SDO_CENTROID'
 
21
DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE'
 
22
DISTANCE = 'SDO_GEOM.SDO_DISTANCE'
 
23
EXTENT = 'SDO_AGGR_MBR'
 
24
INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION'
 
25
LENGTH = 'SDO_GEOM.SDO_LENGTH'
 
26
NUM_GEOM = 'SDO_UTIL.GETNUMELEM'
 
27
NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES'
 
28
POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE'
 
29
SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR'
 
30
TRANSFORM = 'SDO_CS.TRANSFORM'
 
31
UNION = 'SDO_GEOM.SDO_UNION'
 
32
UNIONAGG = 'SDO_AGGR_UNION'
 
33
 
 
34
# We want to get SDO Geometries as WKT because it is much easier to 
 
35
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.  
 
36
# However, this adversely affects performance (i.e., Java is called 
 
37
# to convert to WKT on every query).  If someone wishes to write a 
 
38
# SDO_GEOMETRY(...) parser in Python, let me know =)
 
39
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
 
40
 
 
41
#### Classes used in constructing Oracle spatial SQL ####
 
42
class SDOOperation(SpatialFunction):
 
43
    "Base class for SDO* Oracle operations."
 
44
    def __init__(self, func, **kwargs):
 
45
        kwargs.setdefault('operator', '=')
 
46
        kwargs.setdefault('result', 'TRUE')
 
47
        kwargs.setdefault('end_subst', ") %s '%s'")
 
48
        super(SDOOperation, self).__init__(func, **kwargs)
 
49
 
 
50
class SDODistance(SpatialFunction):
 
51
    "Class for Distance queries."
 
52
    def __init__(self, op, tolerance=0.05):
 
53
        super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance, 
 
54
                                          operator=op, result='%%s')
 
55
 
 
56
class SDOGeomRelate(SpatialFunction):
 
57
    "Class for using SDO_GEOM.RELATE."
 
58
    def __init__(self, mask, tolerance=0.05):
 
59
        # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
 
60
        # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
 
61
        end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
 
62
        beg_subst = "%%s(%%s, '%s'" % mask 
 
63
        super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
 
64
 
 
65
class SDORelate(SpatialFunction):
 
66
    "Class for using SDO_RELATE."
 
67
    masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
 
68
    mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
 
69
    def __init__(self, mask):
 
70
        func = 'SDO_RELATE'
 
71
        if not self.mask_regex.match(mask):
 
72
            raise ValueError('Invalid %s mask: "%s"' % (func, mask))
 
73
        super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask)
 
74
 
 
75
#### Lookup type mapping dictionaries of Oracle spatial operations ####
 
76
 
 
77
# Valid distance types and substitutions
 
78
dtypes = (Decimal, Distance, float, int, long)
 
79
DISTANCE_FUNCTIONS = {
 
80
    'distance_gt' : (SDODistance('>'), dtypes),
 
81
    'distance_gte' : (SDODistance('>='), dtypes),
 
82
    'distance_lt' : (SDODistance('<'), dtypes),
 
83
    'distance_lte' : (SDODistance('<='), dtypes),
 
84
    'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', 
 
85
                              beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes),
 
86
    }
 
87
 
 
88
ORACLE_GEOMETRY_FUNCTIONS = {
 
89
    'contains' : SDOOperation('SDO_CONTAINS'),
 
90
    'coveredby' : SDOOperation('SDO_COVEREDBY'),
 
91
    'covers' : SDOOperation('SDO_COVERS'),
 
92
    'disjoint' : SDOGeomRelate('DISJOINT'),
 
93
    'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
 
94
    'equals' : SDOOperation('SDO_EQUAL'),
 
95
    'exact' : SDOOperation('SDO_EQUAL'),
 
96
    'overlaps' : SDOOperation('SDO_OVERLAPS'),
 
97
    'same_as' : SDOOperation('SDO_EQUAL'),
 
98
    'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
 
99
    'touches' : SDOOperation('SDO_TOUCH'),
 
100
    'within' : SDOOperation('SDO_INSIDE'),
 
101
    }
 
102
ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
 
103
 
 
104
# This lookup type does not require a mapping.
 
105
MISC_TERMS = ['isnull']
 
106
 
 
107
# Acceptable lookup types for Oracle spatial.
 
108
ORACLE_SPATIAL_TERMS  = ORACLE_GEOMETRY_FUNCTIONS.keys()
 
109
ORACLE_SPATIAL_TERMS += MISC_TERMS
 
110
ORACLE_SPATIAL_TERMS = dict((term, None) for term in ORACLE_SPATIAL_TERMS) # Making dictionary for fast lookups
 
111
 
 
112
#### The `get_geo_where_clause` function for Oracle ####
 
113
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
 
114
    "Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
 
115
    # Getting the quoted table name as `geo_col`.
 
116
    geo_col = '%s.%s' % (qn(table_alias), qn(name))
 
117
 
 
118
    # See if a Oracle Geometry function matches the lookup type next
 
119
    lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
 
120
    if lookup_info:
 
121
        # Lookup types that are tuples take tuple arguments, e.g., 'relate' and 
 
122
        # 'dwithin' lookup types.
 
123
        if isinstance(lookup_info, tuple):
 
124
            # First element of tuple is lookup type, second element is the type
 
125
            # of the expected argument (e.g., str, float)
 
126
            sdo_op, arg_type = lookup_info
 
127
 
 
128
            # Ensuring that a tuple _value_ was passed in from the user
 
129
            if not isinstance(geo_annot.value, tuple):
 
130
                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
 
131
            if len(geo_annot.value) != 2: 
 
132
                raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
 
133
            
 
134
            # Ensuring the argument type matches what we expect.
 
135
            if not isinstance(geo_annot.value[1], arg_type):
 
136
                raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
 
137
 
 
138
            if lookup_type == 'relate':
 
139
                # The SDORelate class handles construction for these queries, 
 
140
                # and verifies the mask argument.
 
141
                return sdo_op(geo_annot.value[1]).as_sql(geo_col)
 
142
            else:
 
143
                # Otherwise, just call the `as_sql` method on the SDOOperation instance.
 
144
                return sdo_op.as_sql(geo_col)
 
145
        else:
 
146
            # Lookup info is a SDOOperation instance, whose `as_sql` method returns
 
147
            # the SQL necessary for the geometry function call. For example:  
 
148
            #  SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
 
149
            return lookup_info.as_sql(geo_col)
 
150
    elif lookup_type == 'isnull':
 
151
        # Handling 'isnull' lookup type
 
152
        return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
 
153
 
 
154
    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))