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) 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): class OracleOperations(BaseSpatialOperations, DatabaseOperations):
name = 'oracle' name = 'oracle'
@ -85,6 +89,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
'Difference': 'SDO_GEOM.SDO_DIFFERENCE', 'Difference': 'SDO_GEOM.SDO_DIFFERENCE',
'Distance': 'SDO_GEOM.SDO_DISTANCE', 'Distance': 'SDO_GEOM.SDO_DISTANCE',
'Intersection': 'SDO_GEOM.SDO_INTERSECTION', 'Intersection': 'SDO_GEOM.SDO_INTERSECTION',
'IsValid': 'SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT',
'Length': 'SDO_GEOM.SDO_LENGTH', 'Length': 'SDO_GEOM.SDO_LENGTH',
'NumGeometries': 'SDO_UTIL.GETNUMELEM', 'NumGeometries': 'SDO_UTIL.GETNUMELEM',
'NumPoints': 'SDO_UTIL.GETNUMVERTICES', 'NumPoints': 'SDO_UTIL.GETNUMVERTICES',
@ -109,6 +114,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
'covers': SDOOperator(func='SDO_COVERS'), 'covers': SDOOperator(func='SDO_COVERS'),
'disjoint': SDODisjoint(), 'disjoint': SDODisjoint(),
'intersects': SDOOperator(func='SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()? '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'), 'equals': SDOOperator(func='SDO_EQUAL'),
'exact': SDOOperator(func='SDO_EQUAL'), 'exact': SDOOperator(func='SDO_EQUAL'),
'overlaps': SDOOperator(func='SDO_OVERLAPS'), 'overlaps': SDOOperator(func='SDO_OVERLAPS'),
@ -128,7 +134,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
unsupported_functions = { unsupported_functions = {
'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG',
'BoundingCircle', 'Envelope', 'BoundingCircle', 'Envelope',
'ForceRHR', 'GeoHash', 'IsValid', 'MakeValid', 'MemSize', 'Scale', 'ForceRHR', 'GeoHash', 'MakeValid', 'MemSize', 'Scale',
'SnapToGrid', 'Translate', 'SnapToGrid', 'Translate',
} }

View File

@ -285,9 +285,13 @@ class Intersection(OracleToleranceMixin, GeoFuncWithGeoParam):
arity = 2 arity = 2
class IsValid(GeoFunc): class IsValid(OracleToleranceMixin, GeoFunc):
output_field_class = BooleanField 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): class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
output_field_class = FloatField output_field_class = FloatField

View File

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

View File

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

View File

@ -298,14 +298,14 @@ intersection between them.
.. versionadded:: 1.10 .. 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. 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. Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
.. versionchanged:: 1.11 .. versionchanged:: 1.11
SpatiaLite support was added. SpatiaLite and Oracle support was added.
``Length`` ``Length``
========== ==========

View File

@ -159,6 +159,10 @@ Minor features
:class:`~django.contrib.gis.db.models.functions.MakeValid` function, and :class:`~django.contrib.gis.db.models.functions.MakeValid` function, and
:lookup:`isvalid` lookup. :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` :mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -295,8 +295,14 @@ class GeoLookupTest(TestCase):
def test_isvalid_lookup(self): def test_isvalid_lookup(self):
invalid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))') 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) State.objects.create(name='invalid', poly=invalid_geom)
self.assertEqual(State.objects.filter(poly__isvalid=False).count(), 1) qs = State.objects.all()
self.assertEqual(State.objects.filter(poly__isvalid=True).count(), State.objects.count() - 1) 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") @skipUnlessDBFeature("supports_left_right_lookups")
def test_left_right_lookups(self): def test_left_right_lookups(self):