1
0
mirror of https://github.com/django/django.git synced 2025-04-16 21:34:37 +00:00

Fixed #36289 -- Fixed bulk_create() crash with nullable geometry fields on PostGIS.

Swapped to an allow list instead of a deny list for field types to
determine if the UNNEST optimization can be enabled to avoid further
surprises with other types that would require further specialization to
adapt.

Regression in a16eedcf9c69d8a11d94cac1811018c5b996d491.

Thanks Joshua Goodwin for the report and Sarah Boyce for the test.
This commit is contained in:
Simon Charette 2025-04-03 11:15:27 -04:00 committed by Mariusz Felisiak
parent 4a824b1313
commit 764af7a3d6
4 changed files with 18 additions and 7 deletions

View File

@ -27,8 +27,8 @@ class InsertUnnest(list):
class SQLInsertCompiler(BaseSQLInsertCompiler):
def assemble_as_sql(self, fields, value_rows):
# Specialize bulk-insertion of literal non-array values through
# UNNEST to reduce the time spent planning the query.
# Specialize bulk-insertion of literal values through UNNEST to
# reduce the time spent planning the query.
if (
# The optimization is not worth doing if there is a single
# row as it will result in the same number of placeholders.
@ -36,15 +36,18 @@ class SQLInsertCompiler(BaseSQLInsertCompiler):
# Lack of fields denote the usage of the DEFAULT keyword
# for the insertion of empty rows.
or any(field is None for field in fields)
# Fields that don't use standard internal types might not be
# unnest'able (e.g. array and geometry types are known to be
# problematic).
or any(
field.get_internal_type() not in self.connection.data_types
for field in fields
)
# Compilable cannot be combined in an array of literal values.
or any(any(hasattr(value, "as_sql") for value in row) for row in value_rows)
):
return super().assemble_as_sql(fields, value_rows)
db_types = [field.db_type(self.connection) for field in fields]
# Abort if any of the fields are arrays as UNNEST indiscriminately
# flatten them instead of reducing their nesting by one.
if any(db_type.endswith("]") for db_type in db_types):
return super().assemble_as_sql(fields, value_rows)
return InsertUnnest(["(%%s)::%s[]" % db_type for db_type in db_types]), [
list(map(list, zip(*value_rows)))
]

View File

@ -15,3 +15,7 @@ Bugfixes
* Fixed a regression in Django 5.2 that caused unnecessary queries when
prefetching nullable foreign key relationships (:ticket:`36290`).
* Fixed a regression in Django 5.2 that caused a crash of
``QuerySet.bulk_create()`` with nullable geometry fields on PostGIS
(:ticket:`36289`).

View File

@ -58,7 +58,7 @@ class SimpleModel(models.Model):
class Point2D(SimpleModel):
point = models.PointField()
point = models.PointField(null=True)
class Point3D(SimpleModel):

View File

@ -206,6 +206,10 @@ class Geo3DTest(Geo3DLoadingHelper, TestCase):
lm.save()
self.assertEqual(3, MultiPoint3D.objects.count())
def test_bulk_create_point_field(self):
objs = Point2D.objects.bulk_create([Point2D(), Point2D()])
self.assertEqual(len(objs), 2)
@skipUnlessDBFeature("supports_3d_functions")
def test_union(self):
"""