diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 97e42d8f50..63f8f9d973 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -518,6 +518,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?
@@ -562,6 +564,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
@@ -570,6 +575,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
 
@@ -616,6 +639,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 df883e3ebb..7b2cdc0aa2 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -52,6 +52,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)