Permalink
Browse files

[1.7.x] Fixed #23537 -- Added Oracle GIS SchemaEditor.

Thanks Shai Berger for review.

Backport of a8f0753 from master
  • Loading branch information...
timgraham committed Sep 18, 2014
1 parent 692df41 commit 9d7a4ea20510a2e35fdeac21d67f3f4c17634c25
@@ -2,6 +2,7 @@
from django.contrib.gis.db.backends.oracle.creation import OracleCreation
from django.contrib.gis.db.backends.oracle.introspection import OracleIntrospection
from django.contrib.gis.db.backends.oracle.operations import OracleOperations
+from django.contrib.gis.db.backends.oracle.schema import OracleGISSchemaEditor
class DatabaseWrapper(OracleDatabaseWrapper):
@@ -10,3 +11,6 @@ def __init__(self, *args, **kwargs):
self.ops = OracleOperations(self)
self.creation = OracleCreation(self)
self.introspection = OracleIntrospection(self)
+
+ def schema_editor(self, *args, **kwargs):
+ return OracleGISSchemaEditor(self, *args, **kwargs)
@@ -142,6 +142,9 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
truncate_params = {'relate': None}
+ def geo_quote_name(self, name):
+ return super(OracleOperations, self).geo_quote_name(name).upper()
+
def convert_extent(self, clob):
if clob:
# Generally, Oracle returns a polygon for the extent -- however,
@@ -0,0 +1,94 @@
+from django.contrib.gis.db.models.fields import GeometryField
+from django.db.backends.oracle.schema import DatabaseSchemaEditor
+from django.db.backends.utils import truncate_name
+
+
+class OracleGISSchemaEditor(DatabaseSchemaEditor):
+ sql_add_geometry_metadata = ("""
+ INSERT INTO USER_SDO_GEOM_METADATA
+ ("TABLE_NAME", "COLUMN_NAME", "DIMINFO", "SRID")
+ VALUES (
+ %(table)s,
+ %(column)s,
+ MDSYS.SDO_DIM_ARRAY(
+ MDSYS.SDO_DIM_ELEMENT('LONG', %(dim0)s, %(dim2)s, %(tolerance)s),
+ MDSYS.SDO_DIM_ELEMENT('LAT', %(dim1)s, %(dim3)s, %(tolerance)s)
+ ),
+ %(srid)s
+ )""")
+ sql_add_spatial_index = 'CREATE INDEX %(index)s ON %(table)s(%(column)s) INDEXTYPE IS MDSYS.SPATIAL_INDEX'
+ sql_drop_spatial_index = 'DROP INDEX %(index)s'
+ sql_clear_geometry_table_metadata = 'DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = %(table)s'
+ sql_clear_geometry_field_metadata = (
+ 'DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = %(table)s '
+ 'AND COLUMN_NAME = %(column)s'
+ )
+
+ def __init__(self, *args, **kwargs):
+ super(OracleGISSchemaEditor, self).__init__(*args, **kwargs)
+ self.geometry_sql = []
+
+ def geo_quote_name(self, name):
+ return self.connection.ops.geo_quote_name(name)
+
+ def column_sql(self, model, field, include_default=False):
+ column_sql = super(OracleGISSchemaEditor, self).column_sql(model, field, include_default)
+ if isinstance(field, GeometryField):
+ db_table = model._meta.db_table
+ self.geometry_sql.append(
+ self.sql_add_geometry_metadata % {
+ 'table': self.geo_quote_name(db_table),
+ 'column': self.geo_quote_name(field.column),
+ 'dim0': field._extent[0],
+ 'dim1': field._extent[1],
+ 'dim2': field._extent[2],
+ 'dim3': field._extent[3],
+ 'tolerance': field._tolerance,
+ 'srid': field.srid,
+ }
+ )
+ if field.spatial_index:
+ self.geometry_sql.append(
+ self.sql_add_spatial_index % {
+ 'index': self.quote_name(self._create_spatial_index_name(model, field)),
+ 'table': self.quote_name(db_table),
+ 'column': self.quote_name(field.column),
+ }
+ )
+ return column_sql
+
+ def create_model(self, model):
+ super(OracleGISSchemaEditor, self).create_model(model)
+ self.run_geometry_sql()
+
+ def delete_model(self, model):
+ super(OracleGISSchemaEditor, self).delete_model(model)
+ self.execute(self.sql_clear_geometry_table_metadata % {
+ 'table': self.geo_quote_name(model._meta.db_table),
+ })
+
+ def add_field(self, model, field):
+ super(OracleGISSchemaEditor, self).add_field(model, field)
+ self.run_geometry_sql()
+
+ def remove_field(self, model, field):
+ if isinstance(field, GeometryField):
+ self.execute(self.sql_clear_geometry_field_metadata % {
+ 'table': self.geo_quote_name(model._meta.db_table),
+ 'column': self.geo_quote_name(field.column),
+ })
+ if field.spatial_index:
+ self.execute(self.sql_drop_spatial_index % {
+ 'index': self.quote_name(self._create_spatial_index_name(model, field)),
+ })
+ super(OracleGISSchemaEditor, self).remove_field(model, field)
+
+ def run_geometry_sql(self):
+ for sql in self.geometry_sql:
+ self.execute(sql)
+ self.geometry_sql = []
+
+ def _create_spatial_index_name(self, model, field):
+ # Oracle doesn't allow object names > 30 characters. Use this scheme
+ # instead of self._create_index_name() for backwards compatibility.
+ return truncate_name('%s_%s_id' % (model._meta.db_table, field.column), 30)
@@ -53,6 +53,17 @@ def set_up_test_model(self):
)]
return self.apply_operations('gis', ProjectState(), operations)
+ def assertGeometryColumnsCount(self, expected_count):
+ table_name = "gis_neighborhood"
+ if connection.features.uppercases_column_names:
+ table_name = table_name.upper()
+ self.assertEqual(
+ GeometryColumns.objects.filter(**{
+ GeometryColumns.table_name_col(): table_name,
+ }).count(),
+ expected_count
+ )
+
def test_add_gis_field(self):
"""
Tests the AddField operation with a GIS-enabled column.
@@ -72,10 +83,7 @@ def test_add_gis_field(self):
# Test GeometryColumns when available
if HAS_GEOMETRY_COLUMNS:
- self.assertEqual(
- GeometryColumns.objects.filter(**{GeometryColumns.table_name_col(): "gis_neighborhood"}).count(),
- 2
- )
+ self.assertGeometryColumnsCount(2)
if self.has_spatial_indexes:
with connection.cursor() as cursor:
@@ -97,10 +105,7 @@ def test_remove_gis_field(self):
# Test GeometryColumns when available
if HAS_GEOMETRY_COLUMNS:
- self.assertEqual(
- GeometryColumns.objects.filter(**{GeometryColumns.table_name_col(): "gis_neighborhood"}).count(),
- 0
- )
+ self.assertGeometryColumnsCount(0)
def test_create_model_spatial_index(self):
self.current_state = self.set_up_test_model()
View
@@ -77,6 +77,9 @@ Bugfixes
* Added ``SchemaEditor`` for MySQL GIS backend so that spatial indexes will be
created for apps with migrations (:ticket:`23538`).
+* Added ``SchemaEditor`` for Oracle GIS backend so that spatial metadata and
+ indexes will be created for apps with migrations (:ticket:`23537`).
+
* Coerced the ``related_name`` model field option to unicode during migration
generation to generate migrations that work with both Python 2 and 3
(:ticket:`23455`).

0 comments on commit 9d7a4ea

Please sign in to comment.