#35032 closed Bug (fixed)
Cannot save record with UUID field after migrating existing UUIDField created in Django < 5.0
Reported by: | Emanuel Andrecut | Owned by: | Emanuel Andrecut |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.0 |
Severity: | Release blocker | Keywords: | uuid, mariadb, UUIDField, DataError |
Cc: | raydeal | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Django 5.0 release notes specifies that on MariaDB 10.7+, UUIDField is now created as UUID column rather than CHAR(32) column and migrations where "char(32)" is hardcoded must be added to keep compatibility for UUIDField<s> created before version 5.0: https://docs.djangoproject.com/en/5.0/releases/5.0/#migrating-existing-uuidfield-on-mariadb-10-7
However after applying such migrations as described in the release notes records cannot be saved anymore:
File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/query.py", line 953, in get_or_create return self.create(**params), True ^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/query.py", line 677, in create obj.save(force_insert=True, using=self.db) File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/base.py", line 814, in save self.save_base( File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/base.py", line 901, in save_base updated = self._save_table( ^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/base.py", line 1059, in _save_table results = self._do_insert( ^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/base.py", line 1100, in _do_insert return manager._insert( ^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/query.py", line 1845, in _insert return query.get_compiler(using=using).execute_sql(returning_fields) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1822, in execute_sql cursor.execute(sql, params) File "/home/manu/venv/lib/python3.11/site-packages/django/db/backends/utils.py", line 79, in execute return self._execute_with_wrappers( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/backends/utils.py", line 92, in _execute_with_wrappers return executor(sql, params, many, context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/backends/utils.py", line 100, in _execute with self.db.wrap_database_errors: File "/home/manu/venv/lib/python3.11/site-packages/django/db/utils.py", line 91, in __exit__ raise dj_exc_value.with_traceback(traceback) from exc_value File "/home/manu/venv/lib/python3.11/site-packages/django/db/backends/utils.py", line 105, in _execute return self.cursor.execute(sql, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/django/db/backends/mysql/base.py", line 75, in execute return self.cursor.execute(query, args) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/MySQLdb/cursors.py", line 179, in execute res = self._query(mogrified_query) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/manu/venv/lib/python3.11/site-packages/MySQLdb/cursors.py", line 330, in _query db.query(q) File "/home/manu/venv/lib/python3.11/site-packages/MySQLdb/connections.py", line 255, in query _mysql.connection.query(self, query) django.db.utils.DataError: (1406, "Data too long for column 'id' at row 1")
That is because, before this commit https://github.com/django/django/commit/7cd187a5ba58d7769039f487faeb9a5a2ff05540, has_native_uuid_field
would return False and after the 5.0 change, saving an UUID for old fields that must still use CHAR(32) will not try to save it using the hex value of UUID: https://github.com/django/django/blob/main/django/db/models/fields/__init__.py#L2737
Change History (6)
comment:1 by , 11 months ago
follow-up: 3 comment:2 by , 11 months ago
Cc: | added |
---|---|
Severity: | Normal → Release blocker |
Triage Stage: | Unreviewed → Accepted |
Thanks for the report! Sounds reasonable, I'm not sure if we want to add workarounds to the Django itself. What do you think about updating release notes? For example:
-
docs/releases/5.0.txt
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 4c86337b74..87fda8d866 100644
a b Django < 5.0 should be replaced with a ``UUIDField`` subclass backed by 508 508 def db_type(self, connection): 509 509 return "char(32)" 510 510 511 512 def get_db_prep_value(self, value, connection, prepared=False): 513 value = super().get_db_prep_value(value, connection, prepared) 514 if value is not None: 515 value = value.hex 516 return value 517 511 518 For example:: 512 519 513 520 class MyModel(models.Model): … … For example:: 516 523 Should become:: 517 524 518 525 class Char32UUIDField(models.UUIDField): 519 def db_type(self, connection): 520 return "char(32)" 526 ... 521 527 522 528 523 529 class MyModel(models.Model):
Regression in 7cd187a5ba58d7769039f487faeb9a5a2ff05540.
follow-up: 4 comment:3 by , 11 months ago
I've dropped the Django changes in the PR and updated class Char32UUIDField in release notes, please check.
Replying to Mariusz Felisiak:
Thanks for the report! Sounds reasonable, I'm not sure if we want to add workarounds to the Django itself. What do you think about updating release notes? For example:
docs/releases/5.0.txt
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 4c86337b74..87fda8d866 100644
a b Django < 5.0 should be replaced with a ``UUIDField`` subclass backed by (this hunk was shorter than expected) 508 508 def db_type(self, connection): 509 509 return "char(32)" 510 511 def get_db_prep_value(self, value, connection, prepared=False): 512 value = super().get_db_prep_value(value, connection, prepared) 513 if value is not None: 514 value = value.hex 515 return value 516 510 517 For example:: 511 518 512 519 class MyModel(models.Model): … … For example:: 516 523 Should become:: 517 524 518 525 class Char32UUIDField(models.UUIDField): 519 def db_type(self, connection): 520 return "char(32)" 526 ... 521 527 522 528 523 529 class MyModel(models.Model): Regression in 7cd187a5ba58d7769039f487faeb9a5a2ff05540.
comment:4 by , 11 months ago
Has patch: | set |
---|---|
Owner: | changed from | to
Status: | new → assigned |
Triage Stage: | Accepted → Ready for checkin |
Replying to Emanuel Andrecut:
I've dropped the Django changes in the PR and updated class Char32UUIDField in release notes, please check.
Looks good, thanks.
I've included a possible fix here: https://github.com/django/django/pull/17604/commits/432d4a28b7c79db18f79f091bd66d428ae606483