Browse Source

Fixed #22423 -- Added support for MySQL operators on real geometries.

Thanks Viswanathan Mahalingam for the report and initial patch, and
Nicke Pope and Tim Graham for the review.
Claude Paroz 6 years ago
parent
commit
15715bf2a2

+ 0 - 2
django/contrib/gis/db/backends/base/features.py

@@ -21,8 +21,6 @@ class BaseSpatialFeatures:
     supports_3d_functions = False
     # Does the database support SRID transform operations?
     supports_transform = True
-    # Do geometric relationship operations operate on real shapes (or only on bounding boxes)?
-    supports_real_shape_operations = True
     # Can geometry fields be null?
     supports_null_geometries = True
     # Are empty geometries supported?

+ 0 - 1
django/contrib/gis/db/backends/mysql/features.py

@@ -12,7 +12,6 @@ class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
     supports_length_geodetic = False
     supports_area_geodetic = False
     supports_transform = False
-    supports_real_shape_operations = False
     supports_null_geometries = False
     supports_num_points_poly = False
 

+ 10 - 12
django/contrib/gis/db/backends/mysql/operations.py

@@ -29,22 +29,20 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
 
     @cached_property
     def gis_operators(self):
-        MBREquals = 'MBREqual' if (
-            self.connection.mysql_is_mariadb or self.connection.mysql_version < (5, 7, 6)
-        ) else 'MBREquals'
         return {
             'bbcontains': SpatialOperator(func='MBRContains'),  # For consistency w/PostGIS API
             'bboverlaps': SpatialOperator(func='MBROverlaps'),  # ...
             'contained': SpatialOperator(func='MBRWithin'),  # ...
-            'contains': SpatialOperator(func='MBRContains'),
-            'disjoint': SpatialOperator(func='MBRDisjoint'),
-            'equals': SpatialOperator(func=MBREquals),
-            'exact': SpatialOperator(func=MBREquals),
-            'intersects': SpatialOperator(func='MBRIntersects'),
-            'overlaps': SpatialOperator(func='MBROverlaps'),
-            'same_as': SpatialOperator(func=MBREquals),
-            'touches': SpatialOperator(func='MBRTouches'),
-            'within': SpatialOperator(func='MBRWithin'),
+            'contains': SpatialOperator(func='ST_Contains'),
+            'crosses': SpatialOperator(func='ST_Crosses'),
+            'disjoint': SpatialOperator(func='ST_Disjoint'),
+            'equals': SpatialOperator(func='ST_Equals'),
+            'exact': SpatialOperator(func='ST_Equals'),
+            'intersects': SpatialOperator(func='ST_Intersects'),
+            'overlaps': SpatialOperator(func='ST_Overlaps'),
+            'same_as': SpatialOperator(func='ST_Equals'),
+            'touches': SpatialOperator(func='ST_Touches'),
+            'within': SpatialOperator(func='ST_Within'),
         }
 
     disallowed_aggregates = (

+ 9 - 15
docs/ref/contrib/gis/db-api.txt

@@ -25,21 +25,15 @@ GeoDjango currently provides the following spatial database backends:
 MySQL Spatial Limitations
 -------------------------
 
-MySQL's spatial extensions only support bounding box operations
-(what MySQL calls minimum bounding rectangles, or MBR).  Specifically,
-`MySQL does not conform to the OGC standard
-<https://dev.mysql.com/doc/refman/en/spatial-relation-functions.html>`_:
-
-    Currently, MySQL does not implement these functions
-    [``Contains``, ``Crosses``, ``Disjoint``, ``Intersects``, ``Overlaps``,
-    ``Touches``, ``Within``]
-    according to the specification.  Those that are implemented return
-    the same result as the corresponding MBR-based functions.
-
-In other words, while spatial lookups such as :lookup:`contains <gis-contains>`
-are available in GeoDjango when using MySQL, the results returned are really
-equivalent to what would be returned when using :lookup:`bbcontains`
-on a different spatial backend.
+Before MySQL 5.6.1, spatial extensions only support bounding box operations
+(what MySQL calls minimum bounding rectangles, or MBR). Specifically, MySQL did
+not conform to the OGC standard. Django supports spatial functions operating on
+real geometries available in modern MySQL versions. However, the spatial
+functions are not as rich as other backends like PostGIS.
+
+.. versionchanged:: 3.0
+
+    Support for spatial functions operating on real geometries was added.
 
 .. warning::
 

+ 56 - 11
docs/ref/contrib/gis/geoquerysets.txt

@@ -148,10 +148,15 @@ Backend     SQL Equivalent
 ==========  ============================
 PostGIS     ``ST_Contains(poly, geom)``
 Oracle      ``SDO_CONTAINS(poly, geom)``
-MySQL       ``MBRContains(poly, geom)``
+MySQL       ``ST_Contains(poly, geom)``
 SpatiaLite  ``Contains(poly, geom)``
 ==========  ============================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBRContains`` and operates only on bounding
+    boxes.
+
 .. fieldlookup:: contains_properly
 
 ``contains_properly``
@@ -233,7 +238,7 @@ SpatiaLite  ``Covers(poly, geom)``
 -----------
 
 *Availability*: `PostGIS <https://postgis.net/docs/ST_Crosses.html>`__,
-SpatiaLite, PGRaster (Conversion)
+MySQL, SpatiaLite, PGRaster (Conversion)
 
 Tests if the geometry field spatially crosses the lookup geometry.
 
@@ -245,9 +250,14 @@ Example::
 Backend     SQL Equivalent
 ==========  ==========================
 PostGIS     ``ST_Crosses(poly, geom)``
+MySQL       ``ST_Crosses(poly, geom)``
 SpatiaLite  ``Crosses(poly, geom)``
 ==========  ==========================
 
+.. versionchanged:: 3.0
+
+    MySQL support was added.
+
 .. fieldlookup:: disjoint
 
 ``disjoint``
@@ -267,10 +277,15 @@ Backend     SQL Equivalent
 ==========  =================================================
 PostGIS     ``ST_Disjoint(poly, geom)``
 Oracle      ``SDO_GEOM.RELATE(poly, 'DISJOINT', geom, 0.05)``
-MySQL       ``MBRDisjoint(poly, geom)``
+MySQL       ``ST_Disjoint(poly, geom)``
 SpatiaLite  ``Disjoint(poly, geom)``
 ==========  =================================================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBRDisjoint`` and operates only on bounding
+    boxes.
+
 .. fieldlookup:: equals
 
 ``equals``
@@ -290,10 +305,15 @@ Backend     SQL Equivalent
 ==========  =================================================
 PostGIS     ``ST_Equals(poly, geom)``
 Oracle      ``SDO_EQUAL(poly, geom)``
-MySQL       ``MBREquals(poly, geom)``
+MySQL       ``ST_Equals(poly, geom)``
 SpatiaLite  ``Equals(poly, geom)``
 ==========  =================================================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBREquals`` and operates only on bounding
+    boxes.
+
 .. fieldlookup:: exact
 .. fieldlookup:: same_as
 
@@ -303,8 +323,8 @@ SpatiaLite  ``Equals(poly, geom)``
 *Availability*: `PostGIS <https://postgis.net/docs/ST_Geometry_Same.html>`__,
 Oracle, MySQL, SpatiaLite, PGRaster (Bilateral)
 
-Tests if the geometry field is "equal" to the lookup geometry. On Oracle and
-SpatiaLite it tests spatial equality, while on MySQL and PostGIS it tests
+Tests if the geometry field is "equal" to the lookup geometry. On Oracle,
+MySQL, and SpatiaLite, it tests spatial equality, while on PostGIS it tests
 equality of bounding boxes.
 
 Example::
@@ -316,10 +336,15 @@ Backend     SQL Equivalent
 ==========  =================================================
 PostGIS     ``poly ~= geom``
 Oracle      ``SDO_EQUAL(poly, geom)``
-MySQL       ``MBREquals(poly, geom)``
+MySQL       ``ST_Equals(poly, geom)``
 SpatiaLite  ``Equals(poly, geom)``
 ==========  =================================================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBREquals`` and operates only on bounding
+    boxes.
+
 .. fieldlookup:: intersects
 
 ``intersects``
@@ -339,10 +364,15 @@ Backend     SQL Equivalent
 ==========  =================================================
 PostGIS     ``ST_Intersects(poly, geom)``
 Oracle      ``SDO_OVERLAPBDYINTERSECT(poly, geom)``
-MySQL       ``MBRIntersects(poly, geom)``
+MySQL       ``ST_Intersects(poly, geom)``
 SpatiaLite  ``Intersects(poly, geom)``
 ==========  =================================================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBRIntersects`` and operates only on
+    bounding boxes.
+
 .. fieldlookup:: isvalid
 
 ``isvalid``
@@ -379,10 +409,15 @@ Backend     SQL Equivalent
 ==========  ============================
 PostGIS     ``ST_Overlaps(poly, geom)``
 Oracle      ``SDO_OVERLAPS(poly, geom)``
-MySQL       ``MBROverlaps(poly, geom)``
+MySQL       ``ST_Overlaps(poly, geom)``
 SpatiaLite  ``Overlaps(poly, geom)``
 ==========  ============================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBROverlaps`` and operates only on bounding
+    boxes.
+
 .. fieldlookup:: relate
 
 ``relate``
@@ -464,11 +499,16 @@ Example::
 Backend     SQL Equivalent
 ==========  ==========================
 PostGIS     ``ST_Touches(poly, geom)``
-MySQL       ``MBRTouches(poly, geom)``
+MySQL       ``ST_Touches(poly, geom)``
 Oracle      ``SDO_TOUCH(poly, geom)``
 SpatiaLite  ``Touches(poly, geom)``
 ==========  ==========================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBRTouches`` and operates only on bounding
+    boxes.
+
 .. fieldlookup:: within
 
 ``within``
@@ -487,11 +527,16 @@ Example::
 Backend     SQL Equivalent
 ==========  ==========================
 PostGIS     ``ST_Within(poly, geom)``
-MySQL       ``MBRWithin(poly, geom)``
+MySQL       ``ST_Within(poly, geom)``
 Oracle      ``SDO_INSIDE(poly, geom)``
 SpatiaLite  ``Within(poly, geom)``
 ==========  ==========================
 
+.. versionchanged:: 3.0
+
+    In older versions, MySQL uses ``MBRWithin`` and operates only on bounding
+    boxes.
+
 .. fieldlookup:: left
 
 ``left``

+ 1 - 1
docs/ref/contrib/gis/install/index.txt

@@ -59,7 +59,7 @@ supported versions, and any notes for each of the supported database backends:
 Database            Library Requirements            Supported Versions  Notes
 ==================  ==============================  ==================  =========================================
 PostgreSQL          GEOS, GDAL, PROJ.4, PostGIS     9.5+                Requires PostGIS.
-MySQL               GEOS, GDAL                      5.6+                Not OGC-compliant; :ref:`limited functionality <mysql-spatial-limitations>`.
+MySQL               GEOS, GDAL                      5.6.1+              :ref:`Limited functionality <mysql-spatial-limitations>`.
 Oracle              GEOS, GDAL                      12.2+               XE not supported.
 SQLite              GEOS, GDAL, PROJ.4, SpatiaLite  3.8.3+              Requires SpatiaLite 4.3+
 ==================  ==============================  ==================  =========================================

+ 2 - 1
docs/releases/3.0.txt

@@ -64,7 +64,8 @@ Minor features
 :mod:`django.contrib.gis`
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
-* ...
+* Allowed MySQL spatial lookup functions to operate on real geometries.
+  Previous support was limited to bounding boxes.
 
 :mod:`django.contrib.messages`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ 9 - 14
tests/gis_tests/geoapp/tests.py

@@ -1,4 +1,5 @@
 import tempfile
+import unittest
 from io import StringIO
 
 from django.contrib.gis import gdal
@@ -226,14 +227,15 @@ class GeoLookupTest(TestCase):
 
     def test_disjoint_lookup(self):
         "Testing the `disjoint` lookup type."
+        if (connection.vendor == 'mysql' and not connection.mysql_is_mariadb and
+                connection.mysql_version < (8, 0, 0)):
+            raise unittest.SkipTest('MySQL < 8 gives different results.')
         ptown = City.objects.get(name='Pueblo')
         qs1 = City.objects.filter(point__disjoint=ptown.point)
         self.assertEqual(7, qs1.count())
-
-        if connection.features.supports_real_shape_operations:
-            qs2 = State.objects.filter(poly__disjoint=ptown.point)
-            self.assertEqual(1, qs2.count())
-            self.assertEqual('Kansas', qs2[0].name)
+        qs2 = State.objects.filter(poly__disjoint=ptown.point)
+        self.assertEqual(1, qs2.count())
+        self.assertEqual('Kansas', qs2[0].name)
 
     def test_contains_contained_lookups(self):
         "Testing the 'contained', 'contains', and 'bbcontains' lookup types."
@@ -271,8 +273,7 @@ class GeoLookupTest(TestCase):
         # Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
         # are not contained in Texas or New Zealand.
         self.assertEqual(len(Country.objects.filter(mpoly__contains=pueblo.point)), 0)  # Query w/GEOSGeometry object
-        self.assertEqual(len(Country.objects.filter(mpoly__contains=okcity.point.wkt)),
-                         0 if connection.features.supports_real_shape_operations else 1)  # Query w/WKT
+        self.assertEqual(len(Country.objects.filter(mpoly__contains=okcity.point.wkt)), 0)  # Query w/WKT
 
         # OK City is contained w/in bounding box of Texas.
         if connection.features.supports_bbcontains_lookup:
@@ -570,13 +571,7 @@ class GeoQuerySetTest(TestCase):
         """
         tex_cities = City.objects.filter(
             point__within=Country.objects.filter(name='Texas').values('mpoly')).order_by('name')
-        expected = ['Dallas', 'Houston']
-        if not connection.features.supports_real_shape_operations:
-            expected.append('Oklahoma City')
-        self.assertEqual(
-            list(tex_cities.values_list('name', flat=True)),
-            expected
-        )
+        self.assertEqual(list(tex_cities.values_list('name', flat=True)), ['Dallas', 'Houston'])
 
     def test_non_concrete_field(self):
         NonConcreteModel.objects.create(point=Point(0, 0), name='name')