import logging from django.contrib.gis.db.models import GeometryField from django.db import OperationalError from django.db.backends.mysql.schema import DatabaseSchemaEditor logger = logging.getLogger("django.contrib.gis") class MySQLGISSchemaEditor(DatabaseSchemaEditor): sql_add_spatial_index = "CREATE SPATIAL INDEX %(index)s ON %(table)s(%(column)s)" def skip_default(self, field): # Geometry fields are stored as BLOB/TEXT, for which MySQL < 8.0.13 # doesn't support defaults. if ( isinstance(field, GeometryField) and not self._supports_limited_data_type_defaults ): 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 _field_indexes_sql(self, model, field): if isinstance(field, GeometryField) and field.spatial_index and not field.null: with self.connection.cursor() as cursor: supports_spatial_index = ( self.connection.introspection.supports_spatial_index( cursor, model._meta.db_table ) ) sql = self._create_spatial_index_sql(model, field) if supports_spatial_index: return [sql] else: logger.error( f"Cannot create SPATIAL INDEX {sql}. Only MyISAM, Aria, and InnoDB " f"support them.", ) return [] return super()._field_indexes_sql(model, field) def remove_field(self, model, field): if isinstance(field, GeometryField) and field.spatial_index and not field.null: sql = self._delete_spatial_index_sql(model, field) try: self.execute(sql) except OperationalError: logger.error( "Couldn't remove spatial index: %s (may be expected " "if your storage engine doesn't support them).", sql, ) super().remove_field(model, field) def _alter_field( self, model, old_field, new_field, old_type, new_type, old_db_params, new_db_params, strict=False, ): super()._alter_field( model, old_field, new_field, old_type, new_type, old_db_params, new_db_params, strict=strict, ) old_field_spatial_index = ( isinstance(old_field, GeometryField) and old_field.spatial_index and not old_field.null ) new_field_spatial_index = ( isinstance(new_field, GeometryField) and new_field.spatial_index and not new_field.null ) if not old_field_spatial_index and new_field_spatial_index: self.execute(self._create_spatial_index_sql(model, new_field)) elif old_field_spatial_index and not new_field_spatial_index: self.execute(self._delete_spatial_index_sql(model, old_field)) def _create_spatial_index_name(self, model, field): return "%s_%s_id" % (model._meta.db_table, field.column) def _create_spatial_index_sql(self, model, field): index_name = self._create_spatial_index_name(model, field) qn = self.connection.ops.quote_name return self.sql_add_spatial_index % { "index": qn(index_name), "table": qn(model._meta.db_table), "column": qn(field.column), } def _delete_spatial_index_sql(self, model, field): index_name = self._create_spatial_index_name(model, field) return self._delete_index_sql(model, index_name)