1
0
mirror of https://github.com/django/django.git synced 2025-03-06 15:32:33 +00:00

Fixed #36086 -- Fixed crash when using GeneratedField with non-AutoField pk.

The previous logic was systematically attempting to retrieve last_insert_id
even for models without an AutoField primary key when they had a GeneratedField
on backends that can't return columns from INSERT.

The issue affected MySQL, SQLite < 3.35, and Oracle when the use_returning_into
option was disabled and could result in either crashes when the non-auto
primary key wasn't an IntegerField subclass or silent misassignment of bogus
insert ids (0 or the previous auto primary key insert value) to the first
defined generated field value.
This commit is contained in:
Simon Charette 2025-01-11 01:08:35 -05:00 committed by Sarah Boyce
parent 20eb4bca7d
commit 9e55201555
3 changed files with 30 additions and 4 deletions

View File

@ -8,7 +8,7 @@ from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
from django.db import DatabaseError, NotSupportedError
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import ColPairs, F, OrderBy, RawSQL, Ref, Value
from django.db.models.fields import composite
from django.db.models.fields import AutoField, composite
from django.db.models.functions import Cast, Random
from django.db.models.lookups import Lookup
from django.db.models.query_utils import select_related_descend
@ -1910,17 +1910,23 @@ class SQLInsertCompiler(SQLCompiler):
)
]
cols = [field.get_col(opts.db_table) for field in self.returning_fields]
else:
cols = [opts.pk.get_col(opts.db_table)]
elif returning_fields and isinstance(
returning_field := returning_fields[0], AutoField
):
cols = [returning_field.get_col(opts.db_table)]
rows = [
(
self.connection.ops.last_insert_id(
cursor,
opts.db_table,
opts.pk.column,
returning_field.column,
),
)
]
else:
# Backend doesn't support returning fields and no auto-field
# that can be retrieved from `last_insert_id` was specified.
return []
converters = self.get_converters(cols)
if converters:
rows = self.apply_converters(rows, converters)

View File

@ -527,6 +527,19 @@ class GeneratedModel(models.Model):
required_db_features = {"supports_stored_generated_columns"}
class GeneratedModelNonAutoPk(models.Model):
id = models.IntegerField(primary_key=True)
a = models.IntegerField()
b = models.GeneratedField(
expression=F("a"),
output_field=models.IntegerField(),
db_persist=True,
)
class Meta:
required_db_features = {"supports_stored_generated_columns"}
class GeneratedModelVirtual(models.Model):
a = models.IntegerField()
b = models.IntegerField()

View File

@ -22,6 +22,7 @@ from .models import (
GeneratedModelCheckConstraint,
GeneratedModelCheckConstraintVirtual,
GeneratedModelFieldWithConverters,
GeneratedModelNonAutoPk,
GeneratedModelNull,
GeneratedModelNullVirtual,
GeneratedModelOutputFieldDbCollation,
@ -356,6 +357,12 @@ class StoredGeneratedFieldTests(GeneratedFieldTestMixin, TestCase):
obj = self._refresh_if_needed(obj)
self.assertEqual(obj.field, obj.field_copy)
def test_create_with_non_auto_pk(self):
obj = GeneratedModelNonAutoPk.objects.create(id=1, a=2)
self.assertEqual(obj.id, 1)
self.assertEqual(obj.a, 2)
self.assertEqual(obj.b, 2)
@skipUnlessDBFeature("supports_virtual_generated_columns")
class VirtualGeneratedFieldTests(GeneratedFieldTestMixin, TestCase):