mirror of https://github.com/django/django.git
Added feature flags for introspection capabilities.
This commit is contained in:
parent
cff59bedc2
commit
99d9fa329a
|
@ -518,6 +518,8 @@ class BaseDatabaseFeatures(object):
|
||||||
supports_subqueries_in_group_by = True
|
supports_subqueries_in_group_by = True
|
||||||
supports_bitwise_or = True
|
supports_bitwise_or = True
|
||||||
|
|
||||||
|
supports_boolean_type = True
|
||||||
|
|
||||||
supports_binary_field = True
|
supports_binary_field = True
|
||||||
|
|
||||||
# Do time/datetime fields have microsecond precision?
|
# Do time/datetime fields have microsecond precision?
|
||||||
|
@ -562,6 +564,9 @@ class BaseDatabaseFeatures(object):
|
||||||
# Does the backend reset sequences between tests?
|
# Does the backend reset sequences between tests?
|
||||||
supports_sequence_reset = True
|
supports_sequence_reset = True
|
||||||
|
|
||||||
|
# Can the backend determine reliably the length of a CharField?
|
||||||
|
can_introspect_max_length = True
|
||||||
|
|
||||||
# Confirm support for introspected foreign keys
|
# Confirm support for introspected foreign keys
|
||||||
# Every database can do this reliably, except MySQL,
|
# Every database can do this reliably, except MySQL,
|
||||||
# which can't do it for MyISAM tables
|
# which can't do it for MyISAM tables
|
||||||
|
@ -570,6 +575,24 @@ class BaseDatabaseFeatures(object):
|
||||||
# Can the backend introspect an AutoField, instead of an IntegerField?
|
# Can the backend introspect an AutoField, instead of an IntegerField?
|
||||||
can_introspect_autofield = False
|
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 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
|
||||||
|
|
||||||
# Support for the DISTINCT ON clause
|
# Support for the DISTINCT ON clause
|
||||||
can_distinct_on_fields = False
|
can_distinct_on_fields = False
|
||||||
|
|
||||||
|
@ -616,6 +639,8 @@ class BaseDatabaseFeatures(object):
|
||||||
# Suffix for backends that don't support "SELECT xxx;" queries.
|
# Suffix for backends that don't support "SELECT xxx;" queries.
|
||||||
bare_select_suffix = ''
|
bare_select_suffix = ''
|
||||||
|
|
||||||
|
lowercases_column_names = False
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
|
|
|
@ -172,11 +172,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
has_select_for_update_nowait = False
|
has_select_for_update_nowait = False
|
||||||
supports_forward_references = False
|
supports_forward_references = False
|
||||||
supports_long_model_names = False
|
supports_long_model_names = False
|
||||||
|
supports_boolean_type = False
|
||||||
# XXX MySQL DB-API drivers currently fail on binary data on Python 3.
|
# XXX MySQL DB-API drivers currently fail on binary data on Python 3.
|
||||||
supports_binary_field = six.PY2
|
supports_binary_field = six.PY2
|
||||||
supports_microsecond_precision = False
|
supports_microsecond_precision = False
|
||||||
supports_regex_backreferencing = False
|
supports_regex_backreferencing = False
|
||||||
supports_date_lookup_using_string = False
|
supports_date_lookup_using_string = False
|
||||||
|
can_introspect_binary_field = False
|
||||||
supports_timezones = False
|
supports_timezones = False
|
||||||
requires_explicit_null_ordering_when_grouping = True
|
requires_explicit_null_ordering_when_grouping = True
|
||||||
allows_auto_pk_0 = False
|
allows_auto_pk_0 = False
|
||||||
|
|
|
@ -111,6 +111,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
has_bulk_insert = True
|
has_bulk_insert = True
|
||||||
supports_tablespaces = True
|
supports_tablespaces = True
|
||||||
supports_sequence_reset = False
|
supports_sequence_reset = False
|
||||||
|
can_introspect_max_length = False
|
||||||
|
can_introspect_time_field = False
|
||||||
atomic_transactions = False
|
atomic_transactions = False
|
||||||
supports_combined_alters = False
|
supports_combined_alters = False
|
||||||
nulls_order_largest = True
|
nulls_order_largest = True
|
||||||
|
@ -118,6 +120,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
connection_persists_old_columns = True
|
connection_persists_old_columns = True
|
||||||
closed_cursor_error_class = InterfaceError
|
closed_cursor_error_class = InterfaceError
|
||||||
bare_select_suffix = " FROM DUAL"
|
bare_select_suffix = " FROM DUAL"
|
||||||
|
lowercases_column_names = True
|
||||||
|
|
||||||
|
|
||||||
class DatabaseOperations(BaseDatabaseOperations):
|
class DatabaseOperations(BaseDatabaseOperations):
|
||||||
|
|
|
@ -52,6 +52,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
uses_savepoints = True
|
uses_savepoints = True
|
||||||
supports_tablespaces = True
|
supports_tablespaces = True
|
||||||
supports_transactions = True
|
supports_transactions = True
|
||||||
|
can_introspect_ip_address_field = True
|
||||||
|
can_introspect_small_integer_field = True
|
||||||
can_distinct_on_fields = True
|
can_distinct_on_fields = True
|
||||||
can_rollback_ddl = True
|
can_rollback_ddl = True
|
||||||
supports_combined_alters = True
|
supports_combined_alters = True
|
||||||
|
|
|
@ -105,6 +105,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
supports_foreign_keys = False
|
supports_foreign_keys = False
|
||||||
supports_check_constraints = False
|
supports_check_constraints = False
|
||||||
autocommits_when_autocommit_is_off = True
|
autocommits_when_autocommit_is_off = True
|
||||||
|
can_introspect_positive_integer_field = True
|
||||||
|
can_introspect_small_integer_field = True
|
||||||
supports_transactions = True
|
supports_transactions = True
|
||||||
atomic_transactions = False
|
atomic_transactions = False
|
||||||
can_rollback_ddl = True
|
can_rollback_ddl = True
|
||||||
|
|
|
@ -2,18 +2,13 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from unittest import expectedFailure, skipUnless
|
from unittest import skipUnless
|
||||||
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
from django.utils.six import PY3, StringIO
|
from django.utils.six import PY3, StringIO
|
||||||
|
|
||||||
if connection.vendor == 'oracle':
|
|
||||||
expectedFailureOnOracle = expectedFailure
|
|
||||||
else:
|
|
||||||
expectedFailureOnOracle = lambda f: f
|
|
||||||
|
|
||||||
|
|
||||||
class InspectDBTestCase(TestCase):
|
class InspectDBTestCase(TestCase):
|
||||||
|
|
||||||
|
@ -44,30 +39,41 @@ class InspectDBTestCase(TestCase):
|
||||||
|
|
||||||
return assertFieldType
|
return assertFieldType
|
||||||
|
|
||||||
# Inspecting Oracle DB doesn't produce correct results, see #19884
|
|
||||||
@expectedFailureOnOracle
|
|
||||||
def test_field_types(self):
|
def test_field_types(self):
|
||||||
"""Test introspection of various Django field types"""
|
"""Test introspection of various Django field types"""
|
||||||
assertFieldType = self.make_field_type_asserter()
|
assertFieldType = self.make_field_type_asserter()
|
||||||
|
|
||||||
assertFieldType('char_field', "models.CharField(max_length=10)")
|
# Inspecting Oracle DB doesn't produce correct results (#19884):
|
||||||
assertFieldType('comma_separated_int_field', "models.CharField(max_length=99)")
|
# - it gets max_length wrong: it returns a number of bytes.
|
||||||
|
# - it reports fields as blank=True when they aren't.
|
||||||
|
if (connection.features.can_introspect_max_length and
|
||||||
|
not connection.features.interprets_empty_strings_as_nulls):
|
||||||
|
assertFieldType('char_field', "models.CharField(max_length=10)")
|
||||||
|
assertFieldType('comma_separated_int_field', "models.CharField(max_length=99)")
|
||||||
assertFieldType('date_field', "models.DateField()")
|
assertFieldType('date_field', "models.DateField()")
|
||||||
assertFieldType('date_time_field', "models.DateTimeField()")
|
assertFieldType('date_time_field', "models.DateTimeField()")
|
||||||
assertFieldType('email_field', "models.CharField(max_length=75)")
|
if (connection.features.can_introspect_max_length and
|
||||||
assertFieldType('file_field', "models.CharField(max_length=100)")
|
not connection.features.interprets_empty_strings_as_nulls):
|
||||||
assertFieldType('file_path_field', "models.CharField(max_length=100)")
|
assertFieldType('email_field', "models.CharField(max_length=75)")
|
||||||
if connection.vendor == 'postgresql':
|
assertFieldType('file_field', "models.CharField(max_length=100)")
|
||||||
# Only PostgreSQL has a specific type
|
assertFieldType('file_path_field', "models.CharField(max_length=100)")
|
||||||
|
if connection.features.can_introspect_ip_address_field:
|
||||||
assertFieldType('ip_address_field', "models.GenericIPAddressField()")
|
assertFieldType('ip_address_field', "models.GenericIPAddressField()")
|
||||||
assertFieldType('gen_ip_adress_field', "models.GenericIPAddressField()")
|
assertFieldType('gen_ip_adress_field', "models.GenericIPAddressField()")
|
||||||
else:
|
elif (connection.features.can_introspect_max_length and
|
||||||
|
not connection.features.interprets_empty_strings_as_nulls):
|
||||||
assertFieldType('ip_address_field', "models.CharField(max_length=15)")
|
assertFieldType('ip_address_field', "models.CharField(max_length=15)")
|
||||||
assertFieldType('gen_ip_adress_field', "models.CharField(max_length=39)")
|
assertFieldType('gen_ip_adress_field', "models.CharField(max_length=39)")
|
||||||
assertFieldType('slug_field', "models.CharField(max_length=50)")
|
if (connection.features.can_introspect_max_length and
|
||||||
assertFieldType('text_field', "models.TextField()")
|
not connection.features.interprets_empty_strings_as_nulls):
|
||||||
assertFieldType('time_field', "models.TimeField()")
|
assertFieldType('slug_field', "models.CharField(max_length=50)")
|
||||||
assertFieldType('url_field', "models.CharField(max_length=200)")
|
if not connection.features.interprets_empty_strings_as_nulls:
|
||||||
|
assertFieldType('text_field', "models.TextField()")
|
||||||
|
if connection.features.can_introspect_time_field:
|
||||||
|
assertFieldType('time_field', "models.TimeField()")
|
||||||
|
if (connection.features.can_introspect_max_length and
|
||||||
|
not connection.features.interprets_empty_strings_as_nulls):
|
||||||
|
assertFieldType('url_field', "models.CharField(max_length=200)")
|
||||||
|
|
||||||
def test_number_field_types(self):
|
def test_number_field_types(self):
|
||||||
"""Test introspection of various Django field types"""
|
"""Test introspection of various Django field types"""
|
||||||
|
@ -75,34 +81,48 @@ class InspectDBTestCase(TestCase):
|
||||||
|
|
||||||
if not connection.features.can_introspect_autofield:
|
if not connection.features.can_introspect_autofield:
|
||||||
assertFieldType('id', "models.IntegerField(primary_key=True) # AutoField?")
|
assertFieldType('id', "models.IntegerField(primary_key=True) # AutoField?")
|
||||||
assertFieldType('big_int_field', "models.BigIntegerField()")
|
|
||||||
if connection.vendor == 'mysql':
|
if connection.features.can_introspect_big_integer_field:
|
||||||
# No native boolean type on MySQL
|
assertFieldType('big_int_field', "models.BigIntegerField()")
|
||||||
assertFieldType('bool_field', "models.IntegerField()")
|
|
||||||
assertFieldType('null_bool_field', "models.IntegerField(blank=True, null=True)")
|
|
||||||
else:
|
else:
|
||||||
|
assertFieldType('big_int_field', "models.IntegerField()")
|
||||||
|
|
||||||
|
if connection.features.supports_boolean_type:
|
||||||
assertFieldType('bool_field', "models.BooleanField()")
|
assertFieldType('bool_field', "models.BooleanField()")
|
||||||
assertFieldType('null_bool_field', "models.NullBooleanField()")
|
assertFieldType('null_bool_field', "models.NullBooleanField()")
|
||||||
|
else:
|
||||||
|
assertFieldType('bool_field', "models.IntegerField()")
|
||||||
|
assertFieldType('null_bool_field', "models.IntegerField(blank=True, null=True)")
|
||||||
|
|
||||||
if connection.vendor == 'sqlite':
|
if connection.vendor == 'sqlite':
|
||||||
# Guessed arguments, see #5014
|
# Guessed arguments on SQLite, see #5014
|
||||||
assertFieldType('decimal_field', "models.DecimalField(max_digits=10, decimal_places=5) "
|
assertFieldType('decimal_field', "models.DecimalField(max_digits=10, decimal_places=5) "
|
||||||
"# max_digits and decimal_places have been guessed, "
|
"# max_digits and decimal_places have been guessed, "
|
||||||
"as this database handles decimal fields as float")
|
"as this database handles decimal fields as float")
|
||||||
else:
|
else:
|
||||||
assertFieldType('decimal_field', "models.DecimalField(max_digits=6, decimal_places=1)")
|
assertFieldType('decimal_field', "models.DecimalField(max_digits=6, decimal_places=1)")
|
||||||
|
|
||||||
assertFieldType('float_field', "models.FloatField()")
|
assertFieldType('float_field', "models.FloatField()")
|
||||||
|
|
||||||
assertFieldType('int_field', "models.IntegerField()")
|
assertFieldType('int_field', "models.IntegerField()")
|
||||||
if connection.vendor == 'sqlite':
|
|
||||||
|
if connection.features.can_introspect_positive_integer_field:
|
||||||
assertFieldType('pos_int_field', "models.PositiveIntegerField()")
|
assertFieldType('pos_int_field', "models.PositiveIntegerField()")
|
||||||
assertFieldType('pos_small_int_field', "models.PositiveSmallIntegerField()")
|
|
||||||
else:
|
else:
|
||||||
# 'unsigned' property undetected on other backends
|
|
||||||
assertFieldType('pos_int_field', "models.IntegerField()")
|
assertFieldType('pos_int_field', "models.IntegerField()")
|
||||||
if connection.vendor == 'postgresql':
|
|
||||||
|
if connection.features.can_introspect_positive_integer_field:
|
||||||
|
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_small_integer_field:
|
||||||
assertFieldType('pos_small_int_field', "models.SmallIntegerField()")
|
assertFieldType('pos_small_int_field', "models.SmallIntegerField()")
|
||||||
else:
|
else:
|
||||||
assertFieldType('pos_small_int_field', "models.IntegerField()")
|
assertFieldType('pos_small_int_field', "models.IntegerField()")
|
||||||
if connection.vendor in ('sqlite', 'postgresql'):
|
|
||||||
|
if connection.features.can_introspect_small_integer_field:
|
||||||
assertFieldType('small_int_field', "models.SmallIntegerField()")
|
assertFieldType('small_int_field', "models.SmallIntegerField()")
|
||||||
else:
|
else:
|
||||||
assertFieldType('small_int_field', "models.IntegerField()")
|
assertFieldType('small_int_field', "models.IntegerField()")
|
||||||
|
@ -156,7 +176,7 @@ class InspectDBTestCase(TestCase):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
call_command('inspectdb', stdout=out)
|
call_command('inspectdb', stdout=out)
|
||||||
output = out.getvalue()
|
output = out.getvalue()
|
||||||
base_name = 'Field' if connection.vendor != 'oracle' else 'field'
|
base_name = 'field' if connection.features.lowercases_column_names else 'Field'
|
||||||
self.assertIn("field = models.IntegerField()", output)
|
self.assertIn("field = models.IntegerField()", output)
|
||||||
self.assertIn("field_field = models.IntegerField(db_column='%s_')" % base_name, output)
|
self.assertIn("field_field = models.IntegerField(db_column='%s_')" % base_name, output)
|
||||||
self.assertIn("field_field_0 = models.IntegerField(db_column='%s__')" % base_name, output)
|
self.assertIn("field_field_0 = models.IntegerField(db_column='%s__')" % base_name, output)
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
|
from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
|
||||||
|
|
||||||
from .models import Reporter, Article
|
from .models import Reporter, Article
|
||||||
|
|
||||||
if connection.vendor == 'oracle':
|
|
||||||
expectedFailureOnOracle = unittest.expectedFailure
|
|
||||||
else:
|
|
||||||
expectedFailureOnOracle = lambda f: f
|
|
||||||
|
|
||||||
|
|
||||||
class IntrospectionTests(TestCase):
|
class IntrospectionTests(TestCase):
|
||||||
def test_table_names(self):
|
def test_table_names(self):
|
||||||
|
@ -61,19 +54,16 @@ class IntrospectionTests(TestCase):
|
||||||
def test_get_table_description_types(self):
|
def test_get_table_description_types(self):
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
|
desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
|
||||||
# The MySQL exception is due to the cursor.description returning the same constant for
|
|
||||||
# text and blob columns. TODO: use information_schema database to retrieve the proper
|
|
||||||
# field type on MySQL
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[datatype(r[1], r) for r in desc],
|
[datatype(r[1], r) for r in desc],
|
||||||
['AutoField' if connection.features.can_introspect_autofield else 'IntegerField',
|
['AutoField' if connection.features.can_introspect_autofield else 'IntegerField',
|
||||||
'CharField', 'CharField', 'CharField', 'BigIntegerField',
|
'CharField', 'CharField', 'CharField', 'BigIntegerField',
|
||||||
'BinaryField' if connection.vendor != 'mysql' else 'TextField']
|
'BinaryField' if connection.features.can_introspect_binary_field else 'TextField']
|
||||||
)
|
)
|
||||||
|
|
||||||
# The following test fails on Oracle due to #17202 (can't correctly
|
# The following test fails on Oracle due to #17202 (can't correctly
|
||||||
# inspect the length of character columns).
|
# inspect the length of character columns).
|
||||||
@expectedFailureOnOracle
|
@skipUnlessDBFeature('can_introspect_max_length')
|
||||||
def test_get_table_description_col_lengths(self):
|
def test_get_table_description_col_lengths(self):
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
|
desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
|
||||||
|
|
Loading…
Reference in New Issue