diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 41a456bfed..0cc60fd450 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -234,6 +234,9 @@ class BaseDatabaseFeatures: # Does the backend support indexing a TextField? supports_index_on_text_field = True + # Does the backend support CAST with precision? + supports_cast_with_precision = True + def __init__(self, connection): self.connection = connection diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index 80af6068ff..c82d565b6e 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -30,6 +30,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): can_clone_databases = True supports_temporal_subtraction = True ignores_table_name_case = True + supports_cast_with_precision = False @cached_property def uses_savepoints(self): diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 260882bf5b..e3a8e85567 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -607,13 +607,16 @@ class Field(RegisterLookupMixin): self.run_validators(value) return value + def db_type_parameters(self, connection): + return DictWrapper(self.__dict__, connection.ops.quote_name, 'qn_') + def db_check(self, connection): """ Return the database column check constraint for this field, for the provided connection. Works the same way as db_type() for the case that get_internal_type() does not map to a preexisting model field. """ - data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") + data = self.db_type_parameters(connection) try: return connection.data_type_check_constraints[self.get_internal_type()] % data except KeyError: @@ -639,7 +642,7 @@ class Field(RegisterLookupMixin): # mapped to one of the built-in Django field types. In this case, you # can implement db_type() instead of get_internal_type() to specify # exactly which wacky database column type you want to use. - data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") + data = self.db_type_parameters(connection) try: return connection.data_types[self.get_internal_type()] % data except KeyError: diff --git a/django/db/models/functions/base.py b/django/db/models/functions/base.py index 82a6083c58..a965f4107c 100644 --- a/django/db/models/functions/base.py +++ b/django/db/models/functions/base.py @@ -10,7 +10,7 @@ class Cast(Func): template = '%(function)s(%(expressions)s AS %(db_type)s)' mysql_types = { - fields.CharField: 'char', + fields.CharField: 'char(%(max_length)s)', fields.IntegerField: 'signed integer', fields.BigIntegerField: 'signed integer', fields.SmallIntegerField: 'signed integer', @@ -31,7 +31,8 @@ class Cast(Func): extra_context = {} output_field_class = type(self.output_field) if output_field_class in self.mysql_types: - extra_context['db_type'] = self.mysql_types[output_field_class] + data = self.output_field.db_type_parameters(connection) + extra_context['db_type'] = self.mysql_types[output_field_class] % data return self.as_sql(compiler, connection, **extra_context) def as_postgresql(self, compiler, connection): diff --git a/tests/db_functions/test_cast.py b/tests/db_functions/test_cast.py index 70de20e5eb..bd4541bf06 100644 --- a/tests/db_functions/test_cast.py +++ b/tests/db_functions/test_cast.py @@ -1,7 +1,7 @@ from django.db import models from django.db.models.expressions import Value from django.db.models.functions import Cast -from django.test import TestCase +from django.test import TestCase, ignore_warnings, skipUnlessDBFeature from .models import Author @@ -19,6 +19,13 @@ class CastTests(TestCase): numbers = Author.objects.annotate(cast_string=Cast('age', models.CharField(max_length=255)),) self.assertEqual(numbers.get().cast_string, '1') + # Silence "Truncated incorrect CHAR(1) value: 'Bob'". + @ignore_warnings(module='django.db.backends.mysql.base') + @skipUnlessDBFeature('supports_cast_with_precision') + def test_cast_to_char_field_with_max_length(self): + names = Author.objects.annotate(cast_string=Cast('name', models.CharField(max_length=1))) + self.assertEqual(names.get().cast_string, 'B') + def test_cast_to_integer(self): for field_class in ( models.IntegerField,