summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2014-05-07 22:14:39 +0200
committerAymeric Augustin <aymeric.augustin@m4x.org>2014-05-08 22:53:21 +0200
commit7194d40236fc9f848f6efd313c1636ba1aceb700 (patch)
treeae154d94ba994c2c1b1b26ac3629118c29df9b69
parent92a1e71100427e0d6e16b9c2bb355ea0a48eb71d (diff)
[1.7.x] Added feature flags for introspection capabilities.
Backport of 99d9fa32 from master
-rw-r--r--django/db/backends/__init__.py25
-rw-r--r--django/db/backends/mysql/base.py2
-rw-r--r--django/db/backends/oracle/base.py3
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py2
-rw-r--r--django/db/backends/sqlite3/base.py2
-rw-r--r--tests/inspectdb/tests.py84
-rw-r--r--tests/introspection/tests.py14
7 files changed, 88 insertions, 44 deletions
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index ffb58fceba..1f223ed8a3 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -616,6 +616,8 @@ class BaseDatabaseFeatures(object):
supports_subqueries_in_group_by = True
supports_bitwise_or = True
+ supports_boolean_type = True
+
supports_binary_field = True
# Do time/datetime fields have microsecond precision?
@@ -660,6 +662,9 @@ class BaseDatabaseFeatures(object):
# Does the backend reset sequences between tests?
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
# Every database can do this reliably, except MySQL,
# which can't do it for MyISAM tables
@@ -668,6 +673,24 @@ class BaseDatabaseFeatures(object):
# 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 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
can_distinct_on_fields = False
@@ -714,6 +737,8 @@ class BaseDatabaseFeatures(object):
# Suffix for backends that don't support "SELECT xxx;" queries.
bare_select_suffix = ''
+ lowercases_column_names = False
+
def __init__(self, connection):
self.connection = connection
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 70d9a29d56..3c71c5792c 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -172,11 +172,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_select_for_update_nowait = False
supports_forward_references = False
supports_long_model_names = False
+ supports_boolean_type = False
# XXX MySQL DB-API drivers currently fail on binary data on Python 3.
supports_binary_field = six.PY2
supports_microsecond_precision = False
supports_regex_backreferencing = False
supports_date_lookup_using_string = False
+ can_introspect_binary_field = False
supports_timezones = False
requires_explicit_null_ordering_when_grouping = True
allows_auto_pk_0 = False
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index e53c8c9fb6..312eb7e40b 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -111,6 +111,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_bulk_insert = True
supports_tablespaces = True
supports_sequence_reset = False
+ can_introspect_max_length = False
+ can_introspect_time_field = False
atomic_transactions = False
supports_combined_alters = False
nulls_order_largest = True
@@ -118,6 +120,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
connection_persists_old_columns = True
closed_cursor_error_class = InterfaceError
bare_select_suffix = " FROM DUAL"
+ lowercases_column_names = True
class DatabaseOperations(BaseDatabaseOperations):
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index 91a183e8b5..17bc8621f9 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -53,6 +53,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
uses_savepoints = True
supports_tablespaces = True
supports_transactions = True
+ can_introspect_ip_address_field = True
+ can_introspect_small_integer_field = True
can_distinct_on_fields = True
can_rollback_ddl = True
supports_combined_alters = True
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 4dcdb1109d..419b394429 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -105,6 +105,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_foreign_keys = False
supports_check_constraints = False
autocommits_when_autocommit_is_off = True
+ can_introspect_positive_integer_field = True
+ can_introspect_small_integer_field = True
supports_transactions = True
atomic_transactions = False
can_rollback_ddl = True
diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py
index 6c5ede7c61..856e63ce4a 100644
--- a/tests/inspectdb/tests.py
+++ b/tests/inspectdb/tests.py
@@ -2,18 +2,13 @@
from __future__ import unicode_literals
import re
-from unittest import expectedFailure, skipUnless
+from unittest import skipUnless
from django.core.management import call_command
from django.db import connection
from django.test import TestCase, skipUnlessDBFeature
from django.utils.six import PY3, StringIO
-if connection.vendor == 'oracle':
- expectedFailureOnOracle = expectedFailure
-else:
- expectedFailureOnOracle = lambda f: f
-
class InspectDBTestCase(TestCase):
@@ -44,30 +39,41 @@ class InspectDBTestCase(TestCase):
return assertFieldType
- # Inspecting Oracle DB doesn't produce correct results, see #19884
- @expectedFailureOnOracle
def test_field_types(self):
"""Test introspection of various Django field types"""
assertFieldType = self.make_field_type_asserter()
- assertFieldType('char_field', "models.CharField(max_length=10)")
- assertFieldType('comma_separated_int_field', "models.CharField(max_length=99)")
+ # Inspecting Oracle DB doesn't produce correct results (#19884):
+ # - 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_time_field', "models.DateTimeField()")
- assertFieldType('email_field', "models.CharField(max_length=75)")
- assertFieldType('file_field', "models.CharField(max_length=100)")
- assertFieldType('file_path_field', "models.CharField(max_length=100)")
- if connection.vendor == 'postgresql':
- # Only PostgreSQL has a specific type
+ if (connection.features.can_introspect_max_length and
+ not connection.features.interprets_empty_strings_as_nulls):
+ assertFieldType('email_field', "models.CharField(max_length=75)")
+ assertFieldType('file_field', "models.CharField(max_length=100)")
+ assertFieldType('file_path_field', "models.CharField(max_length=100)")
+ if connection.features.can_introspect_ip_address_field:
assertFieldType('ip_address_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('gen_ip_adress_field', "models.CharField(max_length=39)")
- assertFieldType('slug_field', "models.CharField(max_length=50)")
- assertFieldType('text_field', "models.TextField()")
- assertFieldType('time_field', "models.TimeField()")
- assertFieldType('url_field', "models.CharField(max_length=200)")
+ if (connection.features.can_introspect_max_length and
+ not connection.features.interprets_empty_strings_as_nulls):
+ assertFieldType('slug_field', "models.CharField(max_length=50)")
+ 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):
"""Test introspection of various Django field types"""
@@ -75,34 +81,48 @@ class InspectDBTestCase(TestCase):
if not connection.features.can_introspect_autofield:
assertFieldType('id', "models.IntegerField(primary_key=True) # AutoField?")
- assertFieldType('big_int_field', "models.BigIntegerField()")
- if connection.vendor == 'mysql':
- # No native boolean type on MySQL
- assertFieldType('bool_field', "models.IntegerField()")
- assertFieldType('null_bool_field', "models.IntegerField(blank=True, null=True)")
+
+ if connection.features.can_introspect_big_integer_field:
+ assertFieldType('big_int_field', "models.BigIntegerField()")
else:
+ assertFieldType('big_int_field', "models.IntegerField()")
+
+ if connection.features.supports_boolean_type:
assertFieldType('bool_field', "models.BooleanField()")
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':
- # Guessed arguments, see #5014
+ # Guessed arguments on SQLite, see #5014
assertFieldType('decimal_field', "models.DecimalField(max_digits=10, decimal_places=5) "
"# max_digits and decimal_places have been guessed, "
"as this database handles decimal fields as float")
else:
assertFieldType('decimal_field', "models.DecimalField(max_digits=6, decimal_places=1)")
+
assertFieldType('float_field', "models.FloatField()")
+
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_small_int_field', "models.PositiveSmallIntegerField()")
else:
- # 'unsigned' property undetected on other backends
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()")
else:
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()")
else:
assertFieldType('small_int_field', "models.IntegerField()")
@@ -156,7 +176,7 @@ class InspectDBTestCase(TestCase):
out = StringIO()
call_command('inspectdb', stdout=out)
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_field = models.IntegerField(db_column='%s_')" % base_name, output)
self.assertIn("field_field_0 = models.IntegerField(db_column='%s__')" % base_name, output)
diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py
index 0c339bc8ea..fe7c3cefba 100644
--- a/tests/introspection/tests.py
+++ b/tests/introspection/tests.py
@@ -1,17 +1,10 @@
from __future__ import unicode_literals
-import unittest
-
from django.db import connection
from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
from .models import Reporter, Article
-if connection.vendor == 'oracle':
- expectedFailureOnOracle = unittest.expectedFailure
-else:
- expectedFailureOnOracle = lambda f: f
-
class IntrospectionTests(TestCase):
def test_table_names(self):
@@ -61,19 +54,16 @@ class IntrospectionTests(TestCase):
def test_get_table_description_types(self):
with connection.cursor() as cursor:
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(
[datatype(r[1], r) for r in desc],
['AutoField' if connection.features.can_introspect_autofield else 'IntegerField',
'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
# inspect the length of character columns).
- @expectedFailureOnOracle
+ @skipUnlessDBFeature('can_introspect_max_length')
def test_get_table_description_col_lengths(self):
with connection.cursor() as cursor:
desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)