mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #23537 -- Added Oracle GIS SchemaEditor.
Thanks Shai Berger for review.
This commit is contained in:
		| @@ -6,6 +6,7 @@ from django.contrib.gis.db.backends.base import BaseSpatialFeatures | |||||||
| from django.contrib.gis.db.backends.oracle.creation import OracleCreation | 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.introspection import OracleIntrospection | ||||||
| from django.contrib.gis.db.backends.oracle.operations import OracleOperations | from django.contrib.gis.db.backends.oracle.operations import OracleOperations | ||||||
|  | from django.contrib.gis.db.backends.oracle.schema import OracleGISSchemaEditor | ||||||
|  |  | ||||||
|  |  | ||||||
| class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures): | class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures): | ||||||
| @@ -20,3 +21,6 @@ class DatabaseWrapper(OracleDatabaseWrapper): | |||||||
|         self.ops = OracleOperations(self) |         self.ops = OracleOperations(self) | ||||||
|         self.creation = OracleCreation(self) |         self.creation = OracleCreation(self) | ||||||
|         self.introspection = OracleIntrospection(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} |     truncate_params = {'relate': None} | ||||||
|  |  | ||||||
|  |     def geo_quote_name(self, name): | ||||||
|  |         return super(OracleOperations, self).geo_quote_name(name).upper() | ||||||
|  |  | ||||||
|     def get_db_converters(self, internal_type): |     def get_db_converters(self, internal_type): | ||||||
|         converters = super(OracleOperations, self).get_db_converters(internal_type) |         converters = super(OracleOperations, self).get_db_converters(internal_type) | ||||||
|         geometry_fields = ( |         geometry_fields = ( | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								django/contrib/gis/db/backends/oracle/schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								django/contrib/gis/db/backends/oracle/schema.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
| @@ -51,6 +51,17 @@ class OperationTests(TransactionTestCase): | |||||||
|         )] |         )] | ||||||
|         return self.apply_operations('gis', ProjectState(), operations) |         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): |     def test_add_gis_field(self): | ||||||
|         """ |         """ | ||||||
|         Tests the AddField operation with a GIS-enabled column. |         Tests the AddField operation with a GIS-enabled column. | ||||||
| @@ -70,10 +81,7 @@ class OperationTests(TransactionTestCase): | |||||||
|  |  | ||||||
|         # Test GeometryColumns when available |         # Test GeometryColumns when available | ||||||
|         if HAS_GEOMETRY_COLUMNS: |         if HAS_GEOMETRY_COLUMNS: | ||||||
|             self.assertEqual( |             self.assertGeometryColumnsCount(2) | ||||||
|                 GeometryColumns.objects.filter(**{GeometryColumns.table_name_col(): "gis_neighborhood"}).count(), |  | ||||||
|                 2 |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         if self.has_spatial_indexes: |         if self.has_spatial_indexes: | ||||||
|             with connection.cursor() as cursor: |             with connection.cursor() as cursor: | ||||||
| @@ -95,10 +103,7 @@ class OperationTests(TransactionTestCase): | |||||||
|  |  | ||||||
|         # Test GeometryColumns when available |         # Test GeometryColumns when available | ||||||
|         if HAS_GEOMETRY_COLUMNS: |         if HAS_GEOMETRY_COLUMNS: | ||||||
|             self.assertEqual( |             self.assertGeometryColumnsCount(0) | ||||||
|                 GeometryColumns.objects.filter(**{GeometryColumns.table_name_col(): "gis_neighborhood"}).count(), |  | ||||||
|                 0 |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|     def test_create_model_spatial_index(self): |     def test_create_model_spatial_index(self): | ||||||
|         self.current_state = self.set_up_test_model() |         self.current_state = self.set_up_test_model() | ||||||
|   | |||||||
| @@ -77,6 +77,9 @@ Bugfixes | |||||||
| * Added ``SchemaEditor`` for MySQL GIS backend so that spatial indexes will be | * Added ``SchemaEditor`` for MySQL GIS backend so that spatial indexes will be | ||||||
|   created for apps with migrations (:ticket:`23538`). |   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 | * Coerced the ``related_name`` model field option to unicode during migration | ||||||
|   generation to generate migrations that work with both Python 2 and 3 |   generation to generate migrations that work with both Python 2 and 3 | ||||||
|   (:ticket:`23455`). |   (:ticket:`23455`). | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user