From d389125606152a6cd57d0f6cadeddf0bd6232215 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 20 Aug 2016 12:14:02 +0200 Subject: [PATCH] Fixed #27098 -- Deprecated DatabaseIntrospection.get_indexes Thanks Akshesh for help with the PostgreSQL query. Thanks Tim Graham for the review. --- .../db/backends/spatialite/introspection.py | 15 +++++++++++---- django/core/management/commands/inspectdb.py | 18 +++++++++--------- django/db/backends/base/introspection.py | 9 ++++----- django/db/backends/mysql/introspection.py | 6 ++++++ django/db/backends/oracle/introspection.py | 7 +++++++ django/db/backends/postgresql/introspection.py | 6 ++++++ django/db/backends/sqlite3/introspection.py | 6 ++++++ docs/internals/deprecation.txt | 2 ++ docs/releases/1.11.txt | 7 +++++++ .../gis_migrations/test_operations.py | 4 ++-- tests/introspection/tests.py | 4 ++++ tests/postgres_tests/test_array.py | 8 ++++++-- tests/schema/tests.py | 18 +++++++++++------- 13 files changed, 81 insertions(+), 29 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/introspection.py b/django/contrib/gis/db/backends/spatialite/introspection.py index c406baafdb..6c3ed1864c 100644 --- a/django/contrib/gis/db/backends/spatialite/introspection.py +++ b/django/contrib/gis/db/backends/spatialite/introspection.py @@ -61,11 +61,18 @@ class SpatiaLiteIntrospection(DatabaseIntrospection): return field_type, field_params - def get_indexes(self, cursor, table_name): - indexes = super(SpatiaLiteIntrospection, self).get_indexes(cursor, table_name) + def get_constraints(self, cursor, table_name): + constraints = super(SpatiaLiteIntrospection, self).get_constraints(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 + constraints['%s__spatial__index' % row[0]] = { + "columns": [row[0]], + "primary_key": False, + "unique": False, + "foreign_key": None, + "check": False, + "index": True, + } + return constraints diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index 02a8ac225e..31079c1a47 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -70,14 +70,15 @@ class Command(BaseCommand): relations = connection.introspection.get_relations(cursor, table_name) except NotImplementedError: relations = {} - try: - indexes = connection.introspection.get_indexes(cursor, table_name) - except NotImplementedError: - indexes = {} try: constraints = connection.introspection.get_constraints(cursor, table_name) except NotImplementedError: constraints = {} + primary_key_column = connection.introspection.get_primary_key_column(cursor, table_name) + unique_columns = [ + c['columns'][0] for c in constraints.values() + if c['unique'] and len(c['columns']) == 1 + ] table_description = connection.introspection.get_table_description(cursor, table_name) except Exception as e: yield "# Unable to inspect table '%s'" % table_name @@ -105,11 +106,10 @@ class Command(BaseCommand): column_to_field_name[column_name] = att_name # Add primary_key and unique, if necessary. - if column_name in indexes: - if indexes[column_name]['primary_key']: - extra_params['primary_key'] = True - elif indexes[column_name]['unique']: - extra_params['unique'] = True + if column_name == primary_key_column: + extra_params['primary_key'] = True + elif column_name in unique_columns: + extra_params['unique'] = True if is_relation: rel_to = ( diff --git a/django/db/backends/base/introspection.py b/django/db/backends/base/introspection.py index 7da14985c3..176a58e243 100644 --- a/django/db/backends/base/introspection.py +++ b/django/db/backends/base/introspection.py @@ -1,7 +1,5 @@ from collections import namedtuple -from django.utils import six - # Structure returned by DatabaseIntrospection.get_table_list() TableInfo = namedtuple('TableInfo', ['name', 'type']) @@ -143,13 +141,14 @@ class BaseDatabaseIntrospection(object): """ Returns the name of the primary key column for the given table. """ - for column in six.iteritems(self.get_indexes(cursor, table_name)): - if column[1]['primary_key']: - return column[0] + for constraint in self.get_constraints(cursor, table_name).values(): + if constraint['primary_key']: + return constraint['columns'][0] return None def get_indexes(self, cursor, table_name): """ + Deprecated in Django 1.11, use get_constraints instead. Returns a dictionary of indexed fieldname -> infodict for the given table, where each infodict is in the format: {'primary_key': boolean representing whether it's the primary key, diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index 3a8b7f6933..fc7ce178fd 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -1,3 +1,4 @@ +import warnings from collections import namedtuple from MySQLdb.constants import FIELD_TYPE @@ -6,6 +7,7 @@ from django.db.backends.base.introspection import ( BaseDatabaseIntrospection, FieldInfo, TableInfo, ) from django.utils.datastructures import OrderedSet +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_text FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('extra', 'default')) @@ -122,6 +124,10 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): return key_columns def get_indexes(self, cursor, table_name): + warnings.warn( + "get_indexes() is deprecated in favor of get_constraints().", + RemovedInDjango21Warning, stacklevel=2 + ) cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(table_name)) # Do a two-pass search for indexes: on first pass check which indexes # are multicolumn, on second pass check which single-column indexes diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py index 5c0c5c5861..2ac43deff6 100644 --- a/django/db/backends/oracle/introspection.py +++ b/django/db/backends/oracle/introspection.py @@ -1,8 +1,11 @@ +import warnings + import cx_Oracle from django.db.backends.base.introspection import ( BaseDatabaseIntrospection, FieldInfo, TableInfo, ) +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_text @@ -117,6 +120,10 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): for row in cursor.fetchall()] def get_indexes(self, cursor, table_name): + warnings.warn( + "get_indexes() is deprecated in favor of get_constraints().", + RemovedInDjango21Warning, stacklevel=2 + ) sql = """ SELECT LOWER(uic1.column_name) AS column_name, CASE user_constraints.constraint_type diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index 8a8465d443..29e11499a9 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -1,10 +1,12 @@ from __future__ import unicode_literals +import warnings from collections import namedtuple from django.db.backends.base.introspection import ( BaseDatabaseIntrospection, FieldInfo, TableInfo, ) +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_text FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('default',)) @@ -124,6 +126,10 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): return key_columns def get_indexes(self, cursor, table_name): + warnings.warn( + "get_indexes() is deprecated in favor of get_constraints().", + RemovedInDjango21Warning, stacklevel=2 + ) # This query retrieves each index on the given table, including the # first associated field name cursor.execute(self._get_indexes_query, [table_name]) diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index db475505d8..2ed629f94a 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -1,9 +1,11 @@ import re +import warnings from collections import namedtuple from django.db.backends.base.introspection import ( BaseDatabaseIntrospection, FieldInfo, TableInfo, ) +from django.utils.deprecation import RemovedInDjango21Warning field_size_re = re.compile(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$') FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('default',)) @@ -183,6 +185,10 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): return key_columns def get_indexes(self, cursor, table_name): + warnings.warn( + "get_indexes() is deprecated in favor of get_constraints().", + RemovedInDjango21Warning, stacklevel=2 + ) indexes = {} for info in self._table_info(cursor, table_name): if info['pk'] != 0: diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 151cdff68d..8be137e635 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -36,6 +36,8 @@ details on these changes. * Silencing of exceptions raised while rendering the ``{% include %}`` template tag will be removed. +* ``DatabaseIntrospection.get_indexes()`` will be removed. + .. _deprecation-removed-in-2.0: 2.0 diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index b2ba159c8a..7b5bcc450f 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -399,6 +399,10 @@ Database backend API dictionaries with a list of ``'ASC'`` and/or ``'DESC'`` values corresponding to the the ordering of each column in the index. +* :djadmin:`inspectdb` no longer calls ``DatabaseIntrospection.get_indexes()`` + which is deprecated. Custom database backends should ensure all types of + indexes are returned by ``DatabaseIntrospection.get_constraints()``. + Dropped support for PostgreSQL 9.2 and PostGIS 2.0 -------------------------------------------------- @@ -549,3 +553,6 @@ Miscellaneous :ttag:`{% include %} ` template tag is deprecated as the behavior is often more confusing than helpful. In Django 2.1, the exception will be raised. + +* ``DatabaseIntrospection.get_indexes()`` is deprecated in favor of + ``DatabaseIntrospection.get_constraints()``. diff --git a/tests/gis_tests/gis_migrations/test_operations.py b/tests/gis_tests/gis_migrations/test_operations.py index aa6b13bdda..84735d8fbd 100644 --- a/tests/gis_tests/gis_migrations/test_operations.py +++ b/tests/gis_tests/gis_migrations/test_operations.py @@ -69,8 +69,8 @@ class OperationTests(TransactionTestCase): def assertSpatialIndexExists(self, table, column): with connection.cursor() as cursor: - indexes = connection.introspection.get_indexes(cursor, table) - self.assertIn(column, indexes) + constraints = connection.introspection.get_constraints(cursor, table) + self.assertIn([column], [c['columns'] for c in constraints.values()]) def alter_gis_model(self, migration_class, model_name, field_name, blank=False, field_class=None): diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py index 68d621d762..7c60902deb 100644 --- a/tests/introspection/tests.py +++ b/tests/introspection/tests.py @@ -5,6 +5,8 @@ from unittest import skipUnless from django.db import connection from django.db.utils import DatabaseError from django.test import TransactionTestCase, mock, skipUnlessDBFeature +from django.test.utils import ignore_warnings +from django.utils.deprecation import RemovedInDjango21Warning from .models import Article, ArticleReporter, City, District, Reporter @@ -169,11 +171,13 @@ class IntrospectionTests(TransactionTestCase): self.assertEqual(primary_key_column, 'id') self.assertEqual(pk_fk_column, 'city_id') + @ignore_warnings(category=RemovedInDjango21Warning) def test_get_indexes(self): with connection.cursor() as cursor: indexes = connection.introspection.get_indexes(cursor, Article._meta.db_table) self.assertEqual(indexes['reporter_id'], {'unique': False, 'primary_key': False}) + @ignore_warnings(category=RemovedInDjango21Warning) def test_get_indexes_multicol(self): """ Test that multicolumn indexes are not included in the introspection diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py index 2b5796dc6f..0b788e0d23 100644 --- a/tests/postgres_tests/test_array.py +++ b/tests/postgres_tests/test_array.py @@ -483,9 +483,13 @@ class TestMigrations(TransactionTestCase): ] # Only the CharField should have a LIKE index. self.assertEqual(like_constraint_columns_list, [['char2']]) - with connection.cursor() as cursor: - indexes = connection.introspection.get_indexes(cursor, table_name) # All fields should have regular indexes. + with connection.cursor() as cursor: + indexes = [ + c['columns'][0] + for c in connection.introspection.get_constraints(cursor, table_name).values() + if c['index'] and len(c['columns']) == 1 + ] self.assertIn('char', indexes) self.assertIn('char2', indexes) self.assertIn('text', indexes) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index ef4d36a0df..9aeada3e67 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -105,12 +105,20 @@ class SchemaTests(TransactionTestCase): raise DatabaseError("Table does not exist (empty pragma)") return columns + def get_primary_key(self, table): + with connection.cursor() as cursor: + return connection.introspection.get_primary_key_column(cursor, table) + def get_indexes(self, table): """ Get the indexes on the table using a new cursor. """ with connection.cursor() as cursor: - return connection.introspection.get_indexes(cursor, table) + return [ + c['columns'][0] + for c in connection.introspection.get_constraints(cursor, table).values() + if c['index'] and len(c['columns']) == 1 + ] def get_constraints(self, table): """ @@ -1685,9 +1693,7 @@ class SchemaTests(TransactionTestCase): with connection.schema_editor() as editor: editor.create_model(Tag) # Ensure the table is there and has the right PK - self.assertTrue( - self.get_indexes(Tag._meta.db_table)['id']['primary_key'], - ) + self.assertEqual(self.get_primary_key(Tag._meta.db_table), 'id') # Alter to change the PK id_field = Tag._meta.get_field("id") old_field = Tag._meta.get_field("slug") @@ -1702,9 +1708,7 @@ class SchemaTests(TransactionTestCase): 'id', self.get_indexes(Tag._meta.db_table), ) - self.assertTrue( - self.get_indexes(Tag._meta.db_table)['slug']['primary_key'], - ) + self.assertEqual(self.get_primary_key(Tag._meta.db_table), 'slug') def test_context_manager_exit(self): """