operations.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. from django.contrib.gis.db.backends.base.adapter import WKTAdapter
  2. from django.contrib.gis.db.backends.base.operations import \
  3. BaseSpatialOperations
  4. from django.contrib.gis.db.backends.utils import SpatialOperator
  5. from django.contrib.gis.db.models import GeometryField, aggregates
  6. from django.db.backends.mysql.operations import DatabaseOperations
  7. from django.utils.functional import cached_property
  8. class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
  9. mysql = True
  10. name = 'mysql'
  11. Adapter = WKTAdapter
  12. @cached_property
  13. def geom_func_prefix(self):
  14. return '' if self.is_mysql_5_5 else 'ST_'
  15. @cached_property
  16. def is_mysql_5_5(self):
  17. return self.connection.mysql_version < (5, 6, 1)
  18. @cached_property
  19. def is_mysql_5_6(self):
  20. return self.connection.mysql_version < (5, 7, 6)
  21. @cached_property
  22. def uses_invalid_empty_geometry_collection(self):
  23. return self.connection.mysql_version >= (5, 7, 5)
  24. @cached_property
  25. def select(self):
  26. return self.geom_func_prefix + 'AsText(%s)'
  27. @cached_property
  28. def from_wkb(self):
  29. return self.geom_func_prefix + 'GeomFromWKB'
  30. @cached_property
  31. def from_text(self):
  32. return self.geom_func_prefix + 'GeomFromText'
  33. @cached_property
  34. def gis_operators(self):
  35. MBREquals = 'MBREqual' if self.is_mysql_5_6 else 'MBREquals'
  36. return {
  37. 'bbcontains': SpatialOperator(func='MBRContains'), # For consistency w/PostGIS API
  38. 'bboverlaps': SpatialOperator(func='MBROverlaps'), # ...
  39. 'contained': SpatialOperator(func='MBRWithin'), # ...
  40. 'contains': SpatialOperator(func='MBRContains'),
  41. 'disjoint': SpatialOperator(func='MBRDisjoint'),
  42. 'equals': SpatialOperator(func=MBREquals),
  43. 'exact': SpatialOperator(func=MBREquals),
  44. 'intersects': SpatialOperator(func='MBRIntersects'),
  45. 'overlaps': SpatialOperator(func='MBROverlaps'),
  46. 'same_as': SpatialOperator(func=MBREquals),
  47. 'touches': SpatialOperator(func='MBRTouches'),
  48. 'within': SpatialOperator(func='MBRWithin'),
  49. }
  50. @cached_property
  51. def function_names(self):
  52. return {'Length': 'GLength'} if self.is_mysql_5_5 else {}
  53. disallowed_aggregates = (
  54. aggregates.Collect, aggregates.Extent, aggregates.Extent3D,
  55. aggregates.MakeLine, aggregates.Union,
  56. )
  57. @cached_property
  58. def unsupported_functions(self):
  59. unsupported = {
  60. 'AsGML', 'AsKML', 'AsSVG', 'BoundingCircle', 'ForceRHR',
  61. 'MakeValid', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse',
  62. 'Scale', 'SnapToGrid', 'Transform', 'Translate',
  63. }
  64. if self.connection.mysql_version < (5, 7, 5):
  65. unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'})
  66. if self.is_mysql_5_5:
  67. unsupported.update({'Difference', 'Distance', 'Intersection', 'SymDifference', 'Union'})
  68. return unsupported
  69. def geo_db_type(self, f):
  70. return f.geom_type
  71. def get_geom_placeholder(self, f, value, compiler):
  72. """
  73. The placeholder here has to include MySQL's WKT constructor. Because
  74. MySQL does not support spatial transformations, there is no need to
  75. modify the placeholder based on the contents of the given value.
  76. """
  77. if hasattr(value, 'as_sql'):
  78. placeholder, _ = compiler.compile(value)
  79. else:
  80. placeholder = '%s(%%s)' % self.from_text
  81. return placeholder
  82. def get_db_converters(self, expression):
  83. converters = super().get_db_converters(expression)
  84. if isinstance(expression.output_field, GeometryField) and self.uses_invalid_empty_geometry_collection:
  85. converters.append(self.convert_invalid_empty_geometry_collection)
  86. return converters
  87. # https://dev.mysql.com/doc/refman/en/spatial-function-argument-handling.html
  88. # MySQL 5.7.5 adds support for the empty geometry collections, but they are represented with invalid WKT.
  89. def convert_invalid_empty_geometry_collection(self, value, expression, connection, context):
  90. if value == b'GEOMETRYCOLLECTION()':
  91. return b'GEOMETRYCOLLECTION EMPTY'
  92. return value