From e198beadad56d5fdd06756b097446b7046a84010 Mon Sep 17 00:00:00 2001
From: Hasan Ramezani <hasan.r67@gmail.com>
Date: Wed, 27 May 2020 00:25:45 +0200
Subject: [PATCH] Fixed #31630 -- Replaced introspection features with
 DatabaseFeatures.introspected_field_types.

---
 django/db/backends/base/features.py       | 48 ++++++++--------------
 django/db/backends/mysql/features.py      | 15 ++++---
 django/db/backends/oracle/features.py     | 14 ++++++-
 django/db/backends/postgresql/features.py | 12 ++++--
 django/db/backends/sqlite3/features.py    | 16 +++++---
 docs/releases/3.2.txt                     | 15 ++++++-
 tests/inspectdb/tests.py                  | 50 +++++------------------
 tests/introspection/tests.py              | 12 +++---
 tests/schema/tests.py                     |  2 +-
 9 files changed, 90 insertions(+), 94 deletions(-)

diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 31113e1c7a..533dbc7fec 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -128,38 +128,22 @@ class BaseDatabaseFeatures:
     # Can the backend introspect an AutoField, instead of an IntegerField?
     can_introspect_autofield = False
 
-    # Can the backend introspect a BigIntegerField, instead of an IntegerField?
-    can_introspect_big_integer_field = True
-
-    # Can the backend introspect an BinaryField, instead of an TextField?
-    can_introspect_binary_field = True
-
-    # Can the backend introspect an DecimalField, instead of an FloatField?
-    can_introspect_decimal_field = True
-
-    # Can the backend introspect a DurationField, instead of a BigIntegerField?
-    can_introspect_duration_field = True
-
-    # Can the backend introspect an IPAddressField, instead of an CharField?
-    can_introspect_ip_address_field = False
-
-    # Can the backend introspect a PositiveIntegerField, instead of an IntegerField?
-    can_introspect_positive_integer_field = False
-
-    # Can the backend introspect a SmallIntegerField, instead of an IntegerField?
-    can_introspect_small_integer_field = False
-
-    # Can the backend introspect a TimeField, instead of a DateTimeField?
-    can_introspect_time_field = True
-
-    # Some backends may not be able to differentiate BigAutoField or
-    # SmallAutoField from other fields such as AutoField.
-    introspected_big_auto_field_type = 'BigAutoField'
-    introspected_small_auto_field_type = 'SmallAutoField'
-
-    # Some backends may not be able to differentiate BooleanField from other
-    # fields such as IntegerField.
-    introspected_boolean_field_type = 'BooleanField'
+    # Map fields which some backends may not be able to differentiate to the
+    # field it's introspected as.
+    introspected_field_types = {
+        'BigAutoField': 'BigAutoField',
+        'BigIntegerField': 'BigIntegerField',
+        'BinaryField': 'BinaryField',
+        'BooleanField': 'BooleanField',
+        'DurationField': 'DurationField',
+        'GenericIPAddressField': 'GenericIPAddressField',
+        'PositiveBigIntegerField': 'PositiveBigIntegerField',
+        'PositiveIntegerField': 'PositiveIntegerField',
+        'PositiveSmallIntegerField': 'PositiveSmallIntegerField',
+        'SmallAutoField': 'SmallAutoField',
+        'SmallIntegerField': 'SmallIntegerField',
+        'TimeField': 'TimeField',
+    }
 
     # Can the backend introspect the column order (ASC/DESC) for indexes?
     supports_index_column_ordering = True
diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py
index 499c303d9e..a1a6e4b084 100644
--- a/django/db/backends/mysql/features.py
+++ b/django/db/backends/mysql/features.py
@@ -15,11 +15,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     supports_regex_backreferencing = False
     supports_date_lookup_using_string = False
     can_introspect_autofield = True
-    can_introspect_binary_field = False
-    can_introspect_duration_field = False
-    can_introspect_small_integer_field = True
-    can_introspect_positive_integer_field = True
-    introspected_boolean_field_type = 'IntegerField'
     supports_index_column_ordering = False
     supports_timezones = False
     requires_explicit_null_ordering_when_grouping = True
@@ -70,6 +65,16 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         "Confirm support for introspected foreign keys"
         return self._mysql_storage_engine != 'MyISAM'
 
+    @cached_property
+    def introspected_field_types(self):
+        return {
+            **super().introspected_field_types,
+            'BinaryField': 'TextField',
+            'BooleanField': 'IntegerField',
+            'DurationField': 'BigIntegerField',
+            'GenericIPAddressField': 'CharField',
+        }
+
     @cached_property
     def can_return_columns_from_insert(self):
         return self.connection.mysql_is_mariadb and self.connection.mysql_version >= (10, 5, 0)
diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py
index bae09559ce..66dfdd4399 100644
--- a/django/db/backends/oracle/features.py
+++ b/django/db/backends/oracle/features.py
@@ -1,5 +1,6 @@
 from django.db import InterfaceError
 from django.db.backends.base.features import BaseDatabaseFeatures
+from django.utils.functional import cached_property
 
 
 class DatabaseFeatures(BaseDatabaseFeatures):
@@ -22,7 +23,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     supports_tablespaces = True
     supports_sequence_reset = False
     can_introspect_materialized_views = True
-    can_introspect_time_field = False
     atomic_transactions = False
     supports_combined_alters = False
     nulls_order_largest = True
@@ -61,3 +61,15 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     allows_multiple_constraints_on_same_fields = False
     supports_boolean_expr_in_select_clause = False
     supports_primitives_in_json_field = False
+
+    @cached_property
+    def introspected_field_types(self):
+        return {
+            **super().introspected_field_types,
+            'GenericIPAddressField': 'CharField',
+            'PositiveBigIntegerField': 'BigIntegerField',
+            'PositiveIntegerField': 'IntegerField',
+            'PositiveSmallIntegerField': 'IntegerField',
+            'SmallIntegerField': 'IntegerField',
+            'TimeField': 'DateTimeField',
+        }
diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index f8d2ea1286..f11558c791 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -22,10 +22,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     can_release_savepoints = True
     supports_tablespaces = True
     supports_transactions = True
-    can_introspect_autofield = True
-    can_introspect_ip_address_field = True
     can_introspect_materialized_views = True
-    can_introspect_small_integer_field = True
     can_distinct_on_fields = True
     can_rollback_ddl = True
     supports_combined_alters = True
@@ -61,6 +58,15 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     supports_deferrable_unique_constraints = True
     has_json_operators = True
 
+    @cached_property
+    def introspected_field_types(self):
+        return {
+            **super().introspected_field_types,
+            'PositiveBigIntegerField': 'BigIntegerField',
+            'PositiveIntegerField': 'IntegerField',
+            'PositiveSmallIntegerField': 'SmallIntegerField',
+        }
+
     @cached_property
     def is_postgresql_10(self):
         return self.connection.pg_version >= 100000
diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index 1b6f99a58c..08c8eb70b9 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/django/db/backends/sqlite3/features.py
@@ -19,12 +19,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     max_query_params = 999
     supports_mixed_date_datetime_comparisons = False
     can_introspect_autofield = True
-    can_introspect_decimal_field = False
-    can_introspect_duration_field = False
-    can_introspect_positive_integer_field = True
-    can_introspect_small_integer_field = True
-    introspected_big_auto_field_type = 'AutoField'
-    introspected_small_auto_field_type = 'AutoField'
     supports_transactions = True
     atomic_transactions = False
     can_rollback_ddl = True
@@ -51,6 +45,16 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     supports_order_by_nulls_modifier = Database.sqlite_version_info >= (3, 30, 0)
     order_by_nulls_first = True
 
+    @cached_property
+    def introspected_field_types(self):
+        return{
+            **super().introspected_field_types,
+            'BigAutoField': 'AutoField',
+            'DurationField': 'BigIntegerField',
+            'GenericIPAddressField': 'CharField',
+            'SmallAutoField': 'AutoField',
+        }
+
     @cached_property
     def supports_json_field(self):
         try:
diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt
index 662fc889e5..c5f5bb7707 100644
--- a/docs/releases/3.2.txt
+++ b/docs/releases/3.2.txt
@@ -247,7 +247,20 @@ Database backend API
 This section describes changes that may be needed in third-party database
 backends.
 
-* ...
+* The new ``DatabaseFeatures.introspected_field_types`` property replaces these
+  features:
+
+  * ``can_introspect_big_integer_field``
+  * ``can_introspect_binary_field``
+  * ``can_introspect_decimal_field``
+  * ``can_introspect_duration_field``
+  * ``can_introspect_ip_address_field``
+  * ``can_introspect_positive_integer_field``
+  * ``can_introspect_small_integer_field``
+  * ``can_introspect_time_field``
+  * ``introspected_big_auto_field_type``
+  * ``introspected_small_auto_field_type``
+  * ``introspected_boolean_field_type``
 
 :mod:`django.contrib.gis`
 -------------------------
diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py
index 910082510a..7ed96abd37 100644
--- a/tests/inspectdb/tests.py
+++ b/tests/inspectdb/tests.py
@@ -60,6 +60,7 @@ class InspectDBTestCase(TestCase):
     def test_field_types(self):
         """Test introspection of various Django field types"""
         assertFieldType = self.make_field_type_asserter()
+        introspected_field_types = connection.features.introspected_field_types
 
         # Inspecting Oracle DB doesn't produce correct results (#19884):
         # - it reports fields as blank=True when they aren't.
@@ -74,12 +75,11 @@ class InspectDBTestCase(TestCase):
             assertFieldType('url_field', "models.CharField(max_length=200)")
         assertFieldType('date_field', "models.DateField()")
         assertFieldType('date_time_field', "models.DateTimeField()")
-        if connection.features.can_introspect_ip_address_field:
+        if introspected_field_types['GenericIPAddressField'] == 'GenericIPAddressField':
             assertFieldType('gen_ip_address_field', "models.GenericIPAddressField()")
         elif not connection.features.interprets_empty_strings_as_nulls:
             assertFieldType('gen_ip_address_field', "models.CharField(max_length=39)")
-        if connection.features.can_introspect_time_field:
-            assertFieldType('time_field', "models.TimeField()")
+        assertFieldType('time_field', 'models.%s()' % introspected_field_types['TimeField'])
         if connection.features.has_native_uuid_field:
             assertFieldType('uuid_field', "models.UUIDField()")
         elif not connection.features.interprets_empty_strings_as_nulls:
@@ -97,20 +97,18 @@ class InspectDBTestCase(TestCase):
     def test_number_field_types(self):
         """Test introspection of various Django field types"""
         assertFieldType = self.make_field_type_asserter()
+        introspected_field_types = connection.features.introspected_field_types
 
         if not connection.features.can_introspect_autofield:
             assertFieldType('id', "models.IntegerField(primary_key=True)  # AutoField?")
 
-        if connection.features.can_introspect_big_integer_field:
-            assertFieldType('big_int_field', "models.BigIntegerField()")
-        else:
-            assertFieldType('big_int_field', "models.IntegerField()")
+        assertFieldType('big_int_field', 'models.%s()' % introspected_field_types['BigIntegerField'])
 
-        bool_field_type = connection.features.introspected_boolean_field_type
+        bool_field_type = introspected_field_types['BooleanField']
         assertFieldType('bool_field', "models.{}()".format(bool_field_type))
         assertFieldType('null_bool_field', 'models.{}(blank=True, null=True)'.format(bool_field_type))
 
-        if connection.features.can_introspect_decimal_field:
+        if connection.vendor != 'sqlite':
             assertFieldType('decimal_field', "models.DecimalField(max_digits=6, decimal_places=1)")
         else:       # Guessed arguments on SQLite, see #5014
             assertFieldType('decimal_field', "models.DecimalField(max_digits=10, decimal_places=5)  "
@@ -118,37 +116,11 @@ class InspectDBTestCase(TestCase):
                                              "as this database handles decimal fields as float")
 
         assertFieldType('float_field', "models.FloatField()")
-
         assertFieldType('int_field', "models.IntegerField()")
-
-        if connection.features.can_introspect_positive_integer_field:
-            assertFieldType('pos_int_field', "models.PositiveIntegerField()")
-        else:
-            assertFieldType('pos_int_field', "models.IntegerField()")
-
-        if connection.features.can_introspect_positive_integer_field:
-            if connection.features.can_introspect_big_integer_field:
-                assertFieldType('pos_big_int_field', 'models.PositiveBigIntegerField()')
-            else:
-                assertFieldType('pos_big_int_field', 'models.PositiveIntegerField()')
-            if connection.features.can_introspect_small_integer_field:
-                assertFieldType('pos_small_int_field', "models.PositiveSmallIntegerField()")
-            else:
-                assertFieldType('pos_small_int_field', "models.PositiveIntegerField()")
-        else:
-            if connection.features.can_introspect_big_integer_field:
-                assertFieldType('pos_big_int_field', 'models.BigIntegerField()')
-            else:
-                assertFieldType('pos_big_int_field', 'models.IntegerField()')
-            if connection.features.can_introspect_small_integer_field:
-                assertFieldType('pos_small_int_field', "models.SmallIntegerField()")
-            else:
-                assertFieldType('pos_small_int_field', "models.IntegerField()")
-
-        if connection.features.can_introspect_small_integer_field:
-            assertFieldType('small_int_field', "models.SmallIntegerField()")
-        else:
-            assertFieldType('small_int_field', "models.IntegerField()")
+        assertFieldType('pos_int_field', 'models.%s()' % introspected_field_types['PositiveIntegerField'])
+        assertFieldType('pos_big_int_field', 'models.%s()' % introspected_field_types['PositiveBigIntegerField'])
+        assertFieldType('pos_small_int_field', 'models.%s()' % introspected_field_types['PositiveSmallIntegerField'])
+        assertFieldType('small_int_field', 'models.%s()' % introspected_field_types['SmallIntegerField'])
 
     @skipUnlessDBFeature('can_introspect_foreign_keys')
     def test_attribute_name_not_python_keyword(self):
diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py
index bafd620232..8ca5c3af3b 100644
--- a/tests/introspection/tests.py
+++ b/tests/introspection/tests.py
@@ -84,10 +84,10 @@ class IntrospectionTests(TransactionTestCase):
                 'CharField',
                 'CharField',
                 'CharField',
-                'BigIntegerField' if connection.features.can_introspect_big_integer_field else 'IntegerField',
-                'BinaryField' if connection.features.can_introspect_binary_field else 'TextField',
-                'SmallIntegerField' if connection.features.can_introspect_small_integer_field else 'IntegerField',
-                'DurationField' if connection.features.can_introspect_duration_field else 'BigIntegerField',
+                connection.features.introspected_field_types['BigIntegerField'],
+                connection.features.introspected_field_types['BinaryField'],
+                connection.features.introspected_field_types['SmallIntegerField'],
+                connection.features.introspected_field_types['DurationField'],
             ]
         )
 
@@ -113,7 +113,7 @@ class IntrospectionTests(TransactionTestCase):
         with connection.cursor() as cursor:
             desc = connection.introspection.get_table_description(cursor, City._meta.db_table)
         self.assertIn(
-            connection.features.introspected_big_auto_field_type,
+            connection.features.introspected_field_types['BigAutoField'],
             [connection.introspection.get_field_type(r[1], r) for r in desc],
         )
 
@@ -122,7 +122,7 @@ class IntrospectionTests(TransactionTestCase):
         with connection.cursor() as cursor:
             desc = connection.introspection.get_table_description(cursor, Country._meta.db_table)
         self.assertIn(
-            connection.features.introspected_small_auto_field_type,
+            connection.features.introspected_field_types['SmallAutoField'],
             [connection.introspection.get_field_type(r[1], r) for r in desc],
         )
 
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 9aa1e239ac..0e479b5f1d 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -559,7 +559,7 @@ class SchemaTests(TransactionTestCase):
         columns = self.column_classes(Author)
         # BooleanField are stored as TINYINT(1) on MySQL.
         field_type = columns['awesome'][0]
-        self.assertEqual(field_type, connection.features.introspected_boolean_field_type)
+        self.assertEqual(field_type, connection.features.introspected_field_types['BooleanField'])
 
     def test_add_field_default_transform(self):
         """