#36086: Creating an instance of a model with a non-auto primary key and a 
generated
field crashes on backends not supporting returning columns from inserts
-------------------------------------+-------------------------------------
               Reporter:  Simon      |          Owner:  Simon Charette
  Charette                           |
                   Type:  Bug        |         Status:  assigned
              Component:  Database   |        Version:  5.0
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 Discovered while working on #36075.

 ---

 The `SQLInsertCompiler.execute_sql` changes
 (7254f1138d9c51fa558229c39c9559b369c4278a) made to accommodate
 `returning_fields` support (#29444) failed to account for the possibility
 that backends not supporting returning columns from inserts (MySQL, SQLite
 < 3.35, and Oracle when the `use_returning_into` option is disabled) could
 still request `returning_fields` and not have an auto-primary key when
 `GeneratedField` was introduced.

 Given the following model

 {{{#!python
 class TestModel(models.Model):
     id = models.UUIDField(primary_key=True, default=uuid.uuid4)
     a = models.IntegerField()
     b = models.GeneratedField(
         expression=F("a"),
         output_field=models.IntegerField(),
         db_persist=True,
     )
 }}}

 Attempting to perform `TestModel.objects.create(a=1)` crashes because even
 if there is no usage of `AutoField`
 
[https://github.com/django/django/blob/8bee7fa45cd7bfe70b68784314e994e2d193fd70/django/db/models/sql/compiler.py#L1926-L1937
 we still attempt to] call `last_insert_id` and for the `UUIDField` primary
 key (which will either return `0` or crash on Oracle since it attempts to
 retrieve a non-existing sequence) and then try to convert `0` to a
 `uuid.UUID` which crashes with


 {{{
 Traceback (most recent call last):
   File "/django/source/tests/model_fields/test_generatedfield.py", line
 361, in test_create_with_non_auto_pk
     obj = GeneratedModelNonAutoPk.objects.create(a=1, b=2)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/django/source/django/db/models/manager.py", line 87, in
 manager_method
     return getattr(self.get_queryset(), name)(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/django/source/django/db/models/query.py", line 663, in create
     obj.save(force_insert=True, using=self.db)
   File "/django/source/django/db/models/base.py", line 901, in save
     self.save_base(
   File "/django/source/django/db/models/base.py", line 1007, in save_base
     updated = self._save_table(
               ^^^^^^^^^^^^^^^^^
   File "/django/source/django/db/models/base.py", line 1168, in
 _save_table
     results = self._do_insert(
               ^^^^^^^^^^^^^^^^
   File "/django/source/django/db/models/base.py", line 1209, in _do_insert
     return manager._insert(
            ^^^^^^^^^^^^^^^^
   File "/django/source/django/db/models/manager.py", line 87, in
 manager_method
     return getattr(self.get_queryset(), name)(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/django/source/django/db/models/query.py", line 1845, in _insert
     return query.get_compiler(using=using).execute_sql(returning_fields)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/django/source/django/db/models/sql/compiler.py", line 1927, in
 execute_sql
     return list(rows)
            ^^^^^^^^^^
   File "/django/source/django/db/models/sql/compiler.py", line 1541, in
 apply_converters
     value = converter(value, expression, connection)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/django/source/django/db/backends/mysql/operations.py", line 327,
 in convert_uuidfield_value
     value = uuid.UUID(value)
             ^^^^^^^^^^^^^^^^
   File "/usr/local/lib/python3.11/uuid.py", line 175, in __init__
     hex = hex.replace('urn:', '').replace('uuid:', '')
           ^^^^^^^^^^^
 AttributeError: 'int' object has no attribute 'replace'
 }}}

 Note that in a case of a non-integer primary, like described above we get
 a crash, but in a case of an integer one the issue is even more insidious
 as the bogus value (which will be either be `0` or whatever value the last
 inserted table with an auto primary key was) will happily pass the
 conversion layer and then be wrongly assigned to `GeneratedField` value.
 In other words, if the model is defined like the following instead

 {{{#!python
 class TestModel(models.Model):
     id = models.IntegerField(primary_key=True)
     a = models.IntegerField()
     b = models.GeneratedField(
         expression=F("a"),
         output_field=models.IntegerField(),
         db_persist=True,
     )
 }}}

 Then we'll encounter the following problem

 {{{#!python
 >>> obj = TestModel.object.create(id=1, a=42)
 >>> obj.b
 1  # This should be 42!!
 }}}
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36086>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019456f76f0b-f5bad3a2-88ba-4ee4-bd45-3f32a32bdb21-000000%40eu-central-1.amazonses.com.

Reply via email to