From da0fb5b1ec89a747459ab64482f3202cb452c068 Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Mon, 10 Jul 2017 16:53:29 +0200 Subject: [PATCH] Fixed #28380 -- Excluded null geometries in SpatiaLite geometry lookups. --- .../gis/db/backends/spatialite/operations.py | 28 +++++++++++-------- tests/gis_tests/geoapp/tests.py | 21 ++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 449e6dcacf..0b5911c83d 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -17,6 +17,12 @@ from django.utils.functional import cached_property from django.utils.version import get_version_tuple +class SpatialiteNullCheckOperator(SpatialOperator): + def as_sql(self, connection, lookup, template_params, sql_params): + sql, params = super().as_sql(connection, lookup, template_params, sql_params) + return '%s > 0' % sql, params + + class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): name = 'spatialite' spatialite = True @@ -33,15 +39,15 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): gis_operators = { # Binary predicates - 'equals': SpatialOperator(func='Equals'), - 'disjoint': SpatialOperator(func='Disjoint'), - 'touches': SpatialOperator(func='Touches'), - 'crosses': SpatialOperator(func='Crosses'), - 'within': SpatialOperator(func='Within'), - 'overlaps': SpatialOperator(func='Overlaps'), - 'contains': SpatialOperator(func='Contains'), - 'intersects': SpatialOperator(func='Intersects'), - 'relate': SpatialOperator(func='Relate'), + 'equals': SpatialiteNullCheckOperator(func='Equals'), + 'disjoint': SpatialiteNullCheckOperator(func='Disjoint'), + 'touches': SpatialiteNullCheckOperator(func='Touches'), + 'crosses': SpatialiteNullCheckOperator(func='Crosses'), + 'within': SpatialiteNullCheckOperator(func='Within'), + 'overlaps': SpatialiteNullCheckOperator(func='Overlaps'), + 'contains': SpatialiteNullCheckOperator(func='Contains'), + 'intersects': SpatialiteNullCheckOperator(func='Intersects'), + 'relate': SpatialiteNullCheckOperator(func='Relate'), # Returns true if B's bounding box completely contains A's bounding box. 'contained': SpatialOperator(func='MbrWithin'), # Returns true if A's bounding box completely contains B's bounding box. @@ -49,8 +55,8 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): # Returns true if A's bounding box overlaps B's bounding box. 'bboverlaps': SpatialOperator(func='MbrOverlaps'), # These are implemented here as synonyms for Equals - 'same_as': SpatialOperator(func='Equals'), - 'exact': SpatialOperator(func='Equals'), + 'same_as': SpatialiteNullCheckOperator(func='Equals'), + 'exact': SpatialiteNullCheckOperator(func='Equals'), # Distance predicates 'dwithin': SpatialOperator(func='PtDistWithin'), } diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index 18bd42368a..f9838b461b 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -403,6 +403,27 @@ class GeoLookupTest(TestCase): State.objects.filter(name='Northern Mariana Islands').update(poly=None) self.assertIsNone(State.objects.get(name='Northern Mariana Islands').poly) + @skipUnlessDBFeature('supports_null_geometries', 'supports_crosses_lookup', 'supports_relate_lookup') + def test_null_geometries_excluded_in_lookups(self): + """NULL features are excluded in spatial lookup functions.""" + null = State.objects.create(name='NULL', poly=None) + queries = [ + ('equals', Point(1, 1)), + ('disjoint', Point(1, 1)), + ('touches', Point(1, 1)), + ('crosses', LineString((0, 0), (1, 1), (5, 5))), + ('within', Point(1, 1)), + ('overlaps', LineString((0, 0), (1, 1), (5, 5))), + ('contains', LineString((0, 0), (1, 1), (5, 5))), + ('intersects', LineString((0, 0), (1, 1), (5, 5))), + ('relate', (Point(1, 1), 'T*T***FF*')), + ('same_as', Point(1, 1)), + ('exact', Point(1, 1)), + ] + for lookup, geom in queries: + with self.subTest(lookup=lookup): + self.assertNotIn(null, State.objects.filter(**{'poly__%s' % lookup: geom})) + @skipUnlessDBFeature("supports_relate_lookup") def test_relate_lookup(self): "Testing the 'relate' lookup type."