Commit 74e7f91e authored by Tim Graham's avatar Tim Graham
Browse files

Fixed #23538 -- Added SchemaEditor for MySQL GIS.

Thanks Claude Paroz for suggestions and review.
parent 215aa4f5
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ from django.contrib.gis.db.backends.base import BaseSpatialFeatures
from django.contrib.gis.db.backends.mysql.creation import MySQLCreation
from django.contrib.gis.db.backends.mysql.introspection import MySQLIntrospection
from django.contrib.gis.db.backends.mysql.operations import MySQLOperations
from django.contrib.gis.db.backends.mysql.schema import MySQLGISSchemaEditor


class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
@@ -25,3 +26,7 @@ class DatabaseWrapper(MySQLDatabaseWrapper):
        self.creation = MySQLCreation(self)
        self.ops = MySQLOperations(self)
        self.introspection = MySQLIntrospection(self)

    def schema_editor(self, *args, **kwargs):
        "Returns a new instance of this backend's SchemaEditor"
        return MySQLGISSchemaEditor(self, *args, **kwargs)
+8 −0
Original line number Diff line number Diff line
@@ -31,3 +31,11 @@ class MySQLIntrospection(DatabaseIntrospection):
            cursor.close()

        return field_type, field_params

    def supports_spatial_index(self, cursor, table_name):
        # Supported with MyISAM, or InnoDB on MySQL 5.7.5+
        storage_engine = self.get_storage_engine(cursor, table_name)
        return (
            (storage_engine == 'InnoDB' and self.connection.mysql_version >= (5, 7, 5)) or
            storage_engine == 'MyISAM'
        )
+70 −0
Original line number Diff line number Diff line
import logging

from django.contrib.gis.db.models.fields import GeometryField
from django.db.utils 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)'
    sql_drop_spatial_index = 'DROP INDEX %(index)s ON %(table)s'

    def __init__(self, *args, **kwargs):
        super(MySQLGISSchemaEditor, self).__init__(*args, **kwargs)
        self.geometry_sql = []

    def column_sql(self, model, field, include_default=False):
        column_sql = super(MySQLGISSchemaEditor, self).column_sql(model, field, include_default)
        # MySQL doesn't support spatial indexes on NULL columns
        if isinstance(field, GeometryField) and field.spatial_index and not field.null:
            qn = self.connection.ops.quote_name
            db_table = model._meta.db_table
            self.geometry_sql.append(
                self.sql_add_spatial_index % {
                    'index': qn(self._create_spatial_index_name(model, field)),
                    'table': qn(db_table),
                    'column': qn(field.column),
                }
            )
        return column_sql

    def create_model(self, model):
        super(MySQLGISSchemaEditor, self).create_model(model)
        self.create_spatial_indexes()

    def add_field(self, model, field):
        super(MySQLGISSchemaEditor, self).add_field(model, field)
        self.create_spatial_indexes()

    def remove_field(self, model, field):
        if isinstance(field, GeometryField) and field.spatial_index:
            qn = self.connection.ops.quote_name
            sql = self.sql_drop_spatial_index % {
                'index': qn(self._create_spatial_index_name(model, field)),
                'table': qn(model._meta.db_table),
            }
            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(MySQLGISSchemaEditor, self).remove_field(model, field)

    def _create_spatial_index_name(self, model, field):
        return '%s_%s_id' % (model._meta.db_table, field.column)

    def create_spatial_indexes(self):
        for sql in self.geometry_sql:
            try:
                self.execute(sql)
            except OperationalError:
                logger.error(
                    "Cannot create SPATIAL INDEX %s. Only MyISAM and (as of "
                    "MySQL 5.7.5) InnoDB support them." % sql
                )
        self.geometry_sql = []
+9 −0
Original line number Diff line number Diff line
@@ -53,3 +53,12 @@ class SpatiaLiteIntrospection(DatabaseIntrospection):
            cursor.close()

        return field_type, field_params

    def get_indexes(self, cursor, table_name):
        indexes = super(SpatiaLiteIntrospection, self).get_indexes(cursor, table_name)
        cursor.execute('SELECT f_geometry_column '
                       'FROM geometry_columns '
                       'WHERE f_table_name=%s AND spatial_index_enabled=1', (table_name,))
        for row in cursor.fetchall():
            indexes[row[0]] = {'primary_key': False, 'unique': False}
        return indexes
+2 −2
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ class Migration(migrations.Migration):
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(max_length=100, unique=True)),
                ('geom', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326, null=True)),
                ('geom', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)),
            ],
            options={
            },
@@ -25,7 +25,7 @@ class Migration(migrations.Migration):
                ('neighborhood', models.ForeignKey(to='gis.Neighborhood', to_field='id', null=True)),
                ('address', models.CharField(max_length=100)),
                ('zip_code', models.IntegerField(null=True, blank=True)),
                ('geom', django.contrib.gis.db.models.fields.PointField(srid=4326, null=True, geography=True)),
                ('geom', django.contrib.gis.db.models.fields.PointField(srid=4326, geography=True)),
            ],
            options={
            },
Loading