summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2014-09-23 14:13:59 -0400
committerTim Graham <timograham@gmail.com>2014-09-25 13:53:44 -0400
commit74e7f91e6de11bc41ecba5d0b2e1b78d03865d32 (patch)
tree0257a2f988de966ba95fe86f4946f31698bfb94f
parent215aa4f53b6bbd07d5c1eecfa94e7fcd00da813e (diff)
Fixed #23538 -- Added SchemaEditor for MySQL GIS.
Thanks Claude Paroz for suggestions and review.
-rw-r--r--django/contrib/gis/db/backends/mysql/base.py5
-rw-r--r--django/contrib/gis/db/backends/mysql/introspection.py8
-rw-r--r--django/contrib/gis/db/backends/mysql/schema.py70
-rw-r--r--django/contrib/gis/db/backends/spatialite/introspection.py9
-rw-r--r--django/contrib/gis/tests/gis_migrations/migrations/0001_initial.py4
-rw-r--r--django/contrib/gis/tests/gis_migrations/test_operations.py27
-rw-r--r--django/db/backends/mysql/introspection.py10
-rw-r--r--docs/releases/1.7.1.txt3
8 files changed, 132 insertions, 4 deletions
diff --git a/django/contrib/gis/db/backends/mysql/base.py b/django/contrib/gis/db/backends/mysql/base.py
index 25ca0da204..d237efc114 100644
--- a/django/contrib/gis/db/backends/mysql/base.py
+++ b/django/contrib/gis/db/backends/mysql/base.py
@@ -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)
diff --git a/django/contrib/gis/db/backends/mysql/introspection.py b/django/contrib/gis/db/backends/mysql/introspection.py
index aae1c1460c..364cdeecfe 100644
--- a/django/contrib/gis/db/backends/mysql/introspection.py
+++ b/django/contrib/gis/db/backends/mysql/introspection.py
@@ -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'
+ )
diff --git a/django/contrib/gis/db/backends/mysql/schema.py b/django/contrib/gis/db/backends/mysql/schema.py
new file mode 100644
index 0000000000..3979182c19
--- /dev/null
+++ b/django/contrib/gis/db/backends/mysql/schema.py
@@ -0,0 +1,70 @@
+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 = []
diff --git a/django/contrib/gis/db/backends/spatialite/introspection.py b/django/contrib/gis/db/backends/spatialite/introspection.py
index 9e1616c514..b81a2e4089 100644
--- a/django/contrib/gis/db/backends/spatialite/introspection.py
+++ b/django/contrib/gis/db/backends/spatialite/introspection.py
@@ -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
diff --git a/django/contrib/gis/tests/gis_migrations/migrations/0001_initial.py b/django/contrib/gis/tests/gis_migrations/migrations/0001_initial.py
index a808e49444..f5d1aade6d 100644
--- a/django/contrib/gis/tests/gis_migrations/migrations/0001_initial.py
+++ b/django/contrib/gis/tests/gis_migrations/migrations/0001_initial.py
@@ -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={
},
diff --git a/django/contrib/gis/tests/gis_migrations/test_operations.py b/django/contrib/gis/tests/gis_migrations/test_operations.py
index d44c22c134..32072eec86 100644
--- a/django/contrib/gis/tests/gis_migrations/test_operations.py
+++ b/django/contrib/gis/tests/gis_migrations/test_operations.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+from django.contrib.gis.tests.utils import mysql
from django.db import connection, migrations, models
from django.db.migrations.migration import Migration
from django.db.migrations.state import ProjectState
@@ -45,7 +46,7 @@ class OperationTests(TransactionTestCase):
[
("id", models.AutoField(primary_key=True)),
('name', models.CharField(max_length=100, unique=True)),
- ('geom', fields.MultiPolygonField(srid=4326, null=True)),
+ ('geom', fields.MultiPolygonField(srid=4326)),
],
)]
return self.apply_operations('gis', ProjectState(), operations)
@@ -58,7 +59,7 @@ class OperationTests(TransactionTestCase):
operation = migrations.AddField(
"Neighborhood",
"path",
- fields.LineStringField(srid=4326, null=True, blank=True),
+ fields.LineStringField(srid=4326),
)
new_state = project_state.clone()
operation.state_forwards("gis", new_state)
@@ -74,6 +75,11 @@ class OperationTests(TransactionTestCase):
2
)
+ if self.has_spatial_indexes:
+ with connection.cursor() as cursor:
+ indexes = connection.introspection.get_indexes(cursor, "gis_neighborhood")
+ self.assertIn('path', indexes)
+
def test_remove_gis_field(self):
"""
Tests the RemoveField operation with a GIS-enabled column.
@@ -93,3 +99,20 @@ class OperationTests(TransactionTestCase):
GeometryColumns.objects.filter(**{GeometryColumns.table_name_col(): "gis_neighborhood"}).count(),
0
)
+
+ def test_create_model_spatial_index(self):
+ self.current_state = self.set_up_test_model()
+
+ if not self.has_spatial_indexes:
+ self.skipTest("No support for Spatial indexes")
+
+ with connection.cursor() as cursor:
+ indexes = connection.introspection.get_indexes(cursor, "gis_neighborhood")
+ self.assertIn('geom', indexes)
+
+ @property
+ def has_spatial_indexes(self):
+ if mysql:
+ with connection.cursor() as cursor:
+ return connection.introspection.supports_spatial_index(cursor, "gis_neighborhood")
+ return True
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index 8b7ff3c1e5..47bb92859a 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -127,6 +127,16 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
indexes[row[4]]['unique'] = True
return indexes
+ def get_storage_engine(self, cursor, table_name):
+ """
+ Retrieves the storage engine for a given table.
+ """
+ cursor.execute(
+ "SELECT engine "
+ "FROM information_schema.tables "
+ "WHERE table_name = %s", [table_name])
+ return cursor.fetchone()[0]
+
def get_constraints(self, cursor, table_name):
"""
Retrieves any constraints or keys (unique, pk, fk, check, index) across one or more columns.
diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt
index a43e1efa8c..a19e19d03b 100644
--- a/docs/releases/1.7.1.txt
+++ b/docs/releases/1.7.1.txt
@@ -73,3 +73,6 @@ Bugfixes
* Fixed bug in migrations that prevented foreign key constraints to unmanaged
models with a custom primary key (:ticket:`23415`).
+
+* Added ``SchemaEditor`` for MySQL GIS backend so that spatial indexes will be
+ created for apps with migrations (:ticket:`23538`).