From e198beadad56d5fdd06756b097446b7046a84010 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani 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): """