diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 32d91699d6..5c34c3b92a 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -575,6 +575,7 @@ class BaseDatabaseFeatures(object): can_return_id_from_insert = False has_bulk_insert = False uses_savepoints = False + can_release_savepoints = True can_combine_inserts_with_and_without_auto_increment_pk = False # If True, don't use integer foreign keys referring to, e.g., positive @@ -610,6 +611,8 @@ class BaseDatabaseFeatures(object): supports_subqueries_in_group_by = True supports_bitwise_or = True + supports_binary_field = True + # Do time/datetime fields have microsecond precision? supports_microsecond_precision = True @@ -703,6 +706,9 @@ class BaseDatabaseFeatures(object): # statements before executing them? requires_sqlparse_for_splitting = True + # Suffix for backends that don't support "SELECT xxx;" queries. + bare_select_suffix = '' + def __init__(self, connection): self.connection = connection diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 85bcd8f79b..70d9a29d56 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -172,6 +172,8 @@ class DatabaseFeatures(BaseDatabaseFeatures): has_select_for_update_nowait = False supports_forward_references = False supports_long_model_names = 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 diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 1a4ff172e8..e46b4f2db8 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -96,6 +96,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): needs_datetime_string_cast = False interprets_empty_strings_as_nulls = True uses_savepoints = True + can_release_savepoints = False has_select_for_update = True has_select_for_update_nowait = True can_return_id_from_insert = True @@ -116,6 +117,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): requires_literal_defaults = True connection_persists_old_columns = True closed_cursor_error_class = InterfaceError + bare_select_suffix = " FROM DUAL" class DatabaseOperations(BaseDatabaseOperations): diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 59db6729df..2c7d02a541 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -3939,8 +3939,7 @@ class UserAdminTest(TestCase): ContentType.objects.clear_cache() expected_queries = 10 - # Oracle doesn't implement "RELEASE SAVPOINT", see #20387. - if connection.vendor == 'oracle': + if not connection.features.can_release_savepoints: expected_queries -= 1 with self.assertNumQueries(expected_queries): @@ -3982,8 +3981,7 @@ class GroupAdminTest(TestCase): g = Group.objects.create(name="test_group") expected_queries = 8 - # Oracle doesn't implement "RELEASE SAVPOINT", see #20387. - if connection.vendor == 'oracle': + if not connection.features.can_release_savepoints: expected_queries -= 1 with self.assertNumQueries(expected_queries): diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 36192af54f..46b991e496 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -131,7 +131,6 @@ class SQLiteTests(TestCase): self.assertRaises(NotImplementedError, models.Item.objects.all().aggregate, aggregate('last_modified')) - def test_convert_values_to_handle_null_value(self): convert_values = DatabaseOperations(connection).convert_values self.assertIsNone(convert_values(None, AutoField(primary_key=True))) @@ -464,9 +463,7 @@ class EscapingChecks(TestCase): EscapingChecksDebug test case, to also test CursorDebugWrapper. """ - # For Oracle, when you want to select a value, you need to specify the - # special pseudo-table 'dual'; a select with no from clause is invalid. - bare_select_suffix = " FROM DUAL" if connection.vendor == 'oracle' else "" + bare_select_suffix = connection.features.bare_select_suffix def test_paramless_no_escaping(self): cursor = connection.cursor() diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 48d3e6a41b..b718986fd2 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -605,6 +605,7 @@ class FileFieldTests(unittest.TestCase): class BinaryFieldTests(test.TestCase): binary_data = b'\x00\x46\xFE' + @test.skipUnlessDBFeature('supports_binary_field') def test_set_and_retrieve(self): data_set = (self.binary_data, six.memoryview(self.binary_data)) for bdata in data_set: @@ -619,10 +620,6 @@ class BinaryFieldTests(test.TestCase): # Test default value self.assertEqual(bytes(dm.short_data), b'\x08') - if connection.vendor == 'mysql' and six.PY3: - # Existing MySQL DB-API drivers fail on binary data. - test_set_and_retrieve = unittest.expectedFailure(test_set_and_retrieve) - def test_max_length(self): dm = DataModel(short_data=self.binary_data * 4) self.assertRaises(ValidationError, dm.full_clean) diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index a2c748e242..5ee2a93a6f 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -10,7 +10,7 @@ from __future__ import unicode_literals import datetime import decimal -from unittest import skip, skipUnless +from unittest import skipUnless import warnings try: @@ -24,7 +24,7 @@ from django.core.serializers.base import DeserializationError from django.core.serializers.xml_serializer import DTDForbidden from django.db import connection, models from django.http import HttpResponse -from django.test import TestCase +from django.test import skipUnlessDBFeature, TestCase from django.utils import six from django.utils.functional import curry @@ -481,8 +481,7 @@ def serializerTest(format, self): for klass, count in instance_count.items(): self.assertEqual(count, klass.objects.count()) -if connection.vendor == 'mysql' and six.PY3: - serializerTest = skip("Existing MySQL DB-API drivers fail on binary data.")(serializerTest) +serializerTest = skipUnlessDBFeature('supports_binary_field')(serializerTest) def naturalKeySerializerTest(format, self):