diff --git a/AUTHORS b/AUTHORS index df2f890761..cf78600873 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,6 +40,7 @@ answer newbie questions, and generally made Django that much better: Akshesh Doshi alang@bright-green.com Alasdair Nicol + Albert Defler Albert Wang Alcides Fonseca Aldian Fazrihady diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 6811b10abb..e2707a9bfd 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -103,7 +103,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): # types, as strings. Column-type strings can contain format strings; they'll # be interpolated against the values of Field.__dict__ before being output. # If a column type is set to None, it won't be included in the output. - data_types = { + + _data_types = { "AutoField": "integer AUTO_INCREMENT", "BigAutoField": "bigint AUTO_INCREMENT", "BinaryField": "longblob", @@ -133,6 +134,13 @@ class DatabaseWrapper(BaseDatabaseWrapper): "UUIDField": "char(32)", } + @cached_property + def data_types(self): + _data_types = self._data_types.copy() + if self.features.has_native_uuid_field: + _data_types["UUIDField"] = "uuid" + return _data_types + # For these data types: # - MySQL < 8.0.13 doesn't accept default values and implicitly treats them # as nullable diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py index 0bb0f91f55..7f733254ee 100644 --- a/django/db/backends/mysql/features.py +++ b/django/db/backends/mysql/features.py @@ -349,3 +349,8 @@ class DatabaseFeatures(BaseDatabaseFeatures): if self.connection.mysql_is_mariadb: return True return self.connection.mysql_version >= (8, 0, 13) + + @cached_property + def has_native_uuid_field(self): + is_mariadb = self.connection.mysql_is_mariadb + return is_mariadb and self.connection.mysql_version >= (10, 7) diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index a5ebf37112..a1ee51a692 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -11,7 +11,8 @@ from django.utils.datastructures import OrderedSet FieldInfo = namedtuple( "FieldInfo", - BaseFieldInfo._fields + ("extra", "is_unsigned", "has_json_constraint", "comment"), + BaseFieldInfo._fields + + ("extra", "is_unsigned", "has_json_constraint", "comment", "data_type"), ) InfoLine = namedtuple( "InfoLine", @@ -62,6 +63,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): return "PositiveIntegerField" elif field_type == "SmallIntegerField": return "PositiveSmallIntegerField" + if description.data_type.upper() == "UUID": + return "UUIDField" # JSON data type is an alias for LONGTEXT in MariaDB, use check # constraints clauses to introspect JSONField. if description.has_json_constraint: @@ -172,6 +175,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): info.is_unsigned, line[0] in json_constraints, info.comment, + info.data_type, ) ) return fields diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 422b08e6ca..4d6164143b 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -1118,7 +1118,10 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): return ( queryset, lambda result: tuple( - getattr(result, "_prefetch_related_val_%s" % f.attname) + f.get_db_prep_value( + getattr(result, f"_prefetch_related_val_{f.attname}"), + connection, + ) for f in fk.local_related_fields ), lambda inst: tuple( diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index d2265f9045..e3cba0c02b 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -377,6 +377,10 @@ Models * :meth:`.QuerySet.aiterator` now supports previous calls to ``prefetch_related()``. +* On MariaDB 10.7+, ``UUIDField`` is now created as ``UUID`` column rather than + ``CHAR(32)`` column. See the migration guide above for more details on + :ref:`migrating-uuidfield`. + Pagination ~~~~~~~~~~ @@ -483,6 +487,38 @@ Using ``create_defaults__exact`` may now be required with ``QuerySet.update_or_c ``create_defaults`` that are used with an ``update_or_create()`` should specify the field in the lookup with ``create_defaults__exact``. +.. _migrating-uuidfield: + +Migrating existing ``UUIDField`` on MariaDB 10.7+ +------------------------------------------------- + +On MariaDB 10.7+, ``UUIDField`` is now created as ``UUID`` column rather than +``CHAR(32)`` column. As a consequence, any ``UUIDField`` created in +Django < 5.0 should be replaced with a ``UUIDField`` subclass backed by +``CHAR(32)``:: + + class Char32UUIDField(models.UUIDField): + def db_type(self, connection): + return "char(32)" + +For example:: + + class MyModel(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) + +Should become:: + + class Char32UUIDField(models.UUIDField): + def db_type(self, connection): + return "char(32)" + + + class MyModel(models.Model): + uuid = Char32UUIDField(primary_key=True, default=uuid.uuid4) + +Running the :djadmin:`makemigrations` command will generate a migration +containing a no-op ``AlterField`` operation. + Miscellaneous -------------