Fixed #27556 -- Added Oracle support for IsValid function and isvalid lookup.

This commit is contained in:
Sergey Fedoseev 2016-11-30 22:22:56 +06:00 committed by Tim Graham
parent e17f40f4b5
commit 4464b9b9ad
7 changed files with 32 additions and 11 deletions

View File

@ -52,6 +52,10 @@ class SDORelate(SpatialOperator):
return super(SDORelate, self).as_sql(connection, lookup, template_params, sql_params)
class SDOIsValid(SpatialOperator):
sql_template = "%%(func)s(%%(lhs)s, %s) = 'TRUE'" % DEFAULT_TOLERANCE
class OracleOperations(BaseSpatialOperations, DatabaseOperations):
name = 'oracle'
@ -85,6 +89,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
'Difference': 'SDO_GEOM.SDO_DIFFERENCE',
'Distance': 'SDO_GEOM.SDO_DISTANCE',
'Intersection': 'SDO_GEOM.SDO_INTERSECTION',
'IsValid': 'SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT',
'Length': 'SDO_GEOM.SDO_LENGTH',
'NumGeometries': 'SDO_UTIL.GETNUMELEM',
'NumPoints': 'SDO_UTIL.GETNUMVERTICES',
@ -109,6 +114,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
'covers': SDOOperator(func='SDO_COVERS'),
'disjoint': SDODisjoint(),
'intersects': SDOOperator(func='SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
'isvalid': SDOIsValid(func='SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT'),
'equals': SDOOperator(func='SDO_EQUAL'),
'exact': SDOOperator(func='SDO_EQUAL'),
'overlaps': SDOOperator(func='SDO_OVERLAPS'),
@ -128,7 +134,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
unsupported_functions = {
'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG',
'BoundingCircle', 'Envelope',
'ForceRHR', 'GeoHash', 'IsValid', 'MakeValid', 'MemSize', 'Scale',
'ForceRHR', 'GeoHash', 'MakeValid', 'MemSize', 'Scale',
'SnapToGrid', 'Translate',
}

View File

@ -285,9 +285,13 @@ class Intersection(OracleToleranceMixin, GeoFuncWithGeoParam):
arity = 2
class IsValid(GeoFunc):
class IsValid(OracleToleranceMixin, GeoFunc):
output_field_class = BooleanField
def as_oracle(self, compiler, connection, **extra_context):
sql, params = super(IsValid, self).as_oracle(compiler, connection, **extra_context)
return "CASE %s WHEN 'TRUE' THEN 1 ELSE 0 END" % sql, params
class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
output_field_class = FloatField

View File

@ -5,7 +5,7 @@ import re
from django.core.exceptions import FieldDoesNotExist
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import Col, Expression
from django.db.models.lookups import BuiltinLookup, Lookup, Transform
from django.db.models.lookups import Lookup, Transform
from django.db.models.sql.query import Query
from django.utils import six
@ -352,15 +352,16 @@ class IntersectsLookup(GISLookup):
gis_lookups['intersects'] = IntersectsLookup
class IsValidLookup(BuiltinLookup):
class IsValidLookup(GISLookup):
lookup_name = 'isvalid'
sql_template = '%(func)s(%(lhs)s)'
def as_sql(self, compiler, connection):
if self.lhs.field.geom_type == 'RASTER':
raise ValueError('The isvalid lookup is only available on geometry fields.')
gis_op = connection.ops.gis_operators[self.lookup_name]
sql, params = self.process_lhs(compiler, connection)
sql = '%(func)s(%(lhs)s)' % {'func': gis_op.func, 'lhs': sql}
sql, params = gis_op.as_sql(connection, self, {'func': gis_op.func, 'lhs': sql}, params)
if not self.rhs:
sql = 'NOT ' + sql
return sql, params

View File

@ -347,7 +347,7 @@ Lookup Type PostGIS Oracle MySQL [#]_ SpatiaLite
:lookup:`equals` X X X X C
:lookup:`exact` X X X X B
:lookup:`intersects` X X X X B
:lookup:`isvalid` X X (LWGEOM)
:lookup:`isvalid` X X X (LWGEOM)
:lookup:`overlaps` X X X X B
:lookup:`relate` X X X C
:lookup:`same_as` X X X X B
@ -390,7 +390,7 @@ Function PostGIS Oracle MySQL SpatiaLite
:class:`ForceRHR` X
:class:`GeoHash` X X (LWGEOM)
:class:`Intersection` X X X (≥ 5.6.1) X
:class:`IsValid` X X (LWGEOM)
:class:`IsValid` X X X (LWGEOM)
:class:`Length` X X X X
:class:`MakeValid` X X (LWGEOM)
:class:`MemSize` X

View File

@ -298,14 +298,14 @@ intersection between them.
.. versionadded:: 1.10
*Availability*: PostGIS, SpatiaLite (LWGEOM)
*Availability*: PostGIS, Oracle, SpatiaLite (LWGEOM)
Accepts a geographic field or expression and tests if the value is well formed.
Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
.. versionchanged:: 1.11
SpatiaLite support was added.
SpatiaLite and Oracle support was added.
``Length``
==========

View File

@ -159,6 +159,10 @@ Minor features
:class:`~django.contrib.gis.db.models.functions.MakeValid` function, and
:lookup:`isvalid` lookup.
* Added Oracle support for the
:class:`~django.contrib.gis.db.models.functions.IsValid` function and
:lookup:`isvalid` lookup.
:mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -295,8 +295,14 @@ class GeoLookupTest(TestCase):
def test_isvalid_lookup(self):
invalid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))')
State.objects.create(name='invalid', poly=invalid_geom)
self.assertEqual(State.objects.filter(poly__isvalid=False).count(), 1)
self.assertEqual(State.objects.filter(poly__isvalid=True).count(), State.objects.count() - 1)
qs = State.objects.all()
if oracle:
# Kansas has adjacent vertices with distance 6.99244813842e-12
# which is smaller than the default Oracle tolerance.
qs = qs.exclude(name='Kansas')
self.assertEqual(State.objects.filter(name='Kansas', poly__isvalid=False).count(), 1)
self.assertEqual(qs.filter(poly__isvalid=False).count(), 1)
self.assertEqual(qs.filter(poly__isvalid=True).count(), qs.count() - 1)
@skipUnlessDBFeature("supports_left_right_lookups")
def test_left_right_lookups(self):