Explorar o código

Fixed #33047 -- Fixed CheckConstraint crash with GIS lookups on PostGIS and MySQL GIS backends.

Thanks Daniel Swain for the report and Arsalan Ghassemi for the initial
patch.

Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Claude Paroz %!s(int64=3) %!d(string=hai) anos
pai
achega
64c3f049ea

+ 5 - 0
django/contrib/gis/db/backends/mysql/schema.py

@@ -22,6 +22,11 @@ class MySQLGISSchemaEditor(DatabaseSchemaEditor):
             return True
         return super().skip_default(field)
 
+    def quote_value(self, value):
+        if isinstance(value, self.connection.ops.Adapter):
+            return super().quote_value(str(value))
+        return super().quote_value(value)
+
     def column_sql(self, model, field, include_default=False):
         column_sql = super().column_sql(model, field, include_default)
         # MySQL doesn't support spatial indexes on NULL columns

+ 11 - 0
django/contrib/gis/db/backends/oracle/features.py

@@ -2,6 +2,7 @@ from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
 from django.db.backends.oracle.features import (
     DatabaseFeatures as OracleDatabaseFeatures,
 )
+from django.utils.functional import cached_property
 
 
 class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures):
@@ -12,3 +13,13 @@ class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures):
     supports_dwithin_distance_expr = False
     supports_tolerance_parameter = True
     unsupported_geojson_options = {'bbox', 'crs', 'precision'}
+
+    @cached_property
+    def django_test_skips(self):
+        skips = super().django_test_skips
+        skips.update({
+            "Oracle doesn't support spatial operators in constraints.": {
+                'gis_tests.gis_migrations.test_operations.OperationTests.test_add_check_constraint',
+            },
+        })
+        return skips

+ 5 - 0
django/contrib/gis/db/backends/oracle/schema.py

@@ -31,6 +31,11 @@ class OracleGISSchemaEditor(DatabaseSchemaEditor):
     def geo_quote_name(self, name):
         return self.connection.ops.geo_quote_name(name)
 
+    def quote_value(self, value):
+        if isinstance(value, self.connection.ops.Adapter):
+            return super().quote_value(str(value))
+        return super().quote_value(value)
+
     def column_sql(self, model, field, include_default=False):
         column_sql = super().column_sql(model, field, include_default)
         if isinstance(field, GeometryField):

+ 4 - 4
django/contrib/gis/db/backends/postgis/adapter.py

@@ -60,10 +60,10 @@ class PostGISAdapter:
         """
         if self.is_geometry:
             # Psycopg will figure out whether to use E'\\000' or '\000'.
-            return '%s(%s)' % (
-                'ST_GeogFromWKB' if self.geography else 'ST_GeomFromEWKB',
-                self._adapter.getquoted().decode()
+            return b'%s(%s)' % (
+                b'ST_GeogFromWKB' if self.geography else b'ST_GeomFromEWKB',
+                self._adapter.getquoted()
             )
         else:
             # For rasters, add explicit type cast to WKB string.
-            return "'%s'::raster" % self.ewkb
+            return b"'%s'::raster" % self.ewkb.encode()

+ 18 - 0
tests/gis_tests/gis_migrations/test_operations.py

@@ -235,6 +235,24 @@ class OperationTests(OperationTestCase):
         )
         self.assertFalse(Neighborhood.objects.first().geom.hasz)
 
+    @skipUnlessDBFeature('supports_column_check_constraints', 'can_introspect_check_constraints')
+    def test_add_check_constraint(self):
+        Neighborhood = self.current_state.apps.get_model('gis', 'Neighborhood')
+        poly = Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
+        constraint = models.CheckConstraint(
+            check=models.Q(geom=poly),
+            name='geom_within_constraint',
+        )
+        Neighborhood._meta.constraints = [constraint]
+        with connection.schema_editor() as editor:
+            editor.add_constraint(Neighborhood, constraint)
+        with connection.cursor() as cursor:
+            constraints = connection.introspection.get_constraints(
+                cursor,
+                Neighborhood._meta.db_table,
+            )
+            self.assertIn('geom_within_constraint', constraints)
+
 
 @skipIfDBFeature('supports_raster')
 class NoRasterSupportTests(OperationTestCase):