web/lib/django/contrib/gis/db/backend/oracle/query.py
changeset 0 0d40e90630ef
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     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))