#36347: bulk_create and postgis geometry field error
-------------------------------------+-------------------------------------
     Reporter:  David Binetti        |                     Type:  Bug
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  5.2                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 I am trying to use `bulk_create` to create/update records on an existing
 model.  One of the fields of this model is a nullable geometry field
 (PointField.). When I run the operation, I get an error that the geometry
 field is not properly formatted, although it is not one of the fields I'm
 updating.  Sample model and query here:

 {{{
 class Person(models.Model):
     id = HashidAutoField(
         primary_key=True,
     )
     name = models.CharField(
         max_length=100,
         blank=False,
         validators=[
             MinLengthValidator(1),
         ]
     )
     external_id = models.IntegerField(
         blank=False,
         null=False,
     )
    geometry = models.PointField(
         null=True,
         blank=True,
    )


 def load_persons(rows):
     person = Person(
         [Person(**row) for row in rows],
         update_conflicts=True,
         update_fields=[
             'name',
         ],
         unique_fields=[
             'external_id',
         ]
     )
     return persons
 }}}

 I then get the following error:

 {{{
 InternalError: parse error - invalid geometry
 LINE 1: ...LL,NULL,NULL,NULL,NULL,NULL,NULL}')::tsvector[], ('{NULL,NUL...
                                                              ^
 HINT:  "NU" <-- parse error at position 2 within geometry
 }}}

 full stacktrace:

 {{{
 ---------------------------------------------------------------------------
 InternalError_                            Traceback (most recent call
 last)
 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/backends/utils.py:105, in CursorWrapper._execute(self,
 sql, params, *ignored_wrapper_args)
     104 else:
 --> 105     return self.cursor.execute(sql, params)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/psycopg/cursor.py:97, in Cursor.execute(self, query, params,
 prepare, binary)
      96 except e._NO_TRACEBACK as ex:
 ---> 97     raise ex.with_traceback(None)
      98 return self

 InternalError_: parse error - invalid geometry
 LINE 1: ...LL,NULL,NULL,NULL,NULL,NULL,NULL}')::tsvector[], ('{NULL,NUL...
                                                              ^
 HINT:  "NU" <-- parse error at position 2 within geometry

 The above exception was the direct cause of the following exception:

 InternalError                             Traceback (most recent call
 last)
 Cell In[7], line 1
 ----> 1 load_persons(rows)

 File ~/repos/registrar/project/app/loaders.py:18, in load_persons(rows)
      17 def load_persons(rows):
 ---> 18     persons = Person.objects.bulk_create(
      19         [Person(**row) for row in rows],
      20         update_conflicts=True,
      21         update_fields=[
      22             'last_name',
      23             'first_name',
      24             'middle_name',
      25             'age',
      26             'gender',
      27             'registered',
      28             'party',
      29             'status',
      30             'imported',
      31         ],
      32         unique_fields=[
      33             'external_id',
      34         ]
      35     )
      36     return persons

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/models/manager.py:87, in
 
BaseManager._get_queryset_methods.<locals>.create_method.<locals>.manager_method(self,
 *args, **kwargs)
      85 @wraps(method)
      86 def manager_method(self, *args, **kwargs):
 ---> 87     return getattr(self.get_queryset(), name)(*args, **kwargs)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/models/query.py:823, in QuerySet.bulk_create(self,
 objs, batch_size, ignore_conflicts, update_conflicts, update_fields,
 unique_fields)
     821 if objs_without_pk:
     822     fields = [f for f in fields if not isinstance(f, AutoField)]
 --> 823     returned_columns = self._batched_insert(
     824         objs_without_pk,
     825         fields,
     826         batch_size,
     827         on_conflict=on_conflict,
     828         update_fields=update_fields,
     829         unique_fields=unique_fields,
     830     )
     831     connection = connections[self.db]
     832     if (
     833         connection.features.can_return_rows_from_bulk_insert
     834         and on_conflict is None
     835     ):

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/models/query.py:1882, in QuerySet._batched_insert(self,
 objs, fields, batch_size, on_conflict, update_fields, unique_fields)
    1877 for item in [objs[i : i + batch_size] for i in range(0, len(objs),
 batch_size)]:
    1878     if bulk_return and (
    1879         on_conflict is None or on_conflict == OnConflict.UPDATE
    1880     ):
    1881         inserted_rows.extend(
 -> 1882             self._insert(
    1883                 item,
    1884                 fields=fields,
    1885                 using=self.db,
    1886                 on_conflict=on_conflict,
    1887                 update_fields=update_fields,
    1888                 unique_fields=unique_fields,
    1889
 returning_fields=self.model._meta.db_returning_fields,
    1890             )
    1891         )
    1892     else:
    1893         self._insert(
    1894             item,
    1895             fields=fields,
    (...)   1899             unique_fields=unique_fields,
    1900         )

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/models/query.py:1854, in QuerySet._insert(self, objs,
 fields, returning_fields, raw, using, on_conflict, update_fields,
 unique_fields)
    1847 query = sql.InsertQuery(
    1848     self.model,
    1849     on_conflict=on_conflict,
    1850     update_fields=update_fields,
    1851     unique_fields=unique_fields,
    1852 )
    1853 query.insert_values(fields, objs, raw=raw)
 -> 1854 return
 query.get_compiler(using=using).execute_sql(returning_fields)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/models/sql/compiler.py:1881, in
 SQLInsertCompiler.execute_sql(self, returning_fields)
    1879 with self.connection.cursor() as cursor:
    1880     for sql, params in self.as_sql():
 -> 1881         cursor.execute(sql, params)
    1882     if not self.returning_fields:
    1883         return []

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/backends/utils.py:122, in
 CursorDebugWrapper.execute(self, sql, params)
     120 def execute(self, sql, params=None):
     121     with self.debug_sql(sql, params,
 use_last_executed_query=True):
 --> 122         return super().execute(sql, params)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/sentry_sdk/utils.py:1811, in
 ensure_integration_enabled.<locals>.patcher.<locals>.runner(*args,
 **kwargs)
    1808 if sentry_sdk.get_client().get_integration(integration) is None:
    1809     return original_function(*args, **kwargs)
 -> 1811 return sentry_patched_function(*args, **kwargs)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/sentry_sdk/integrations/django/__init__.py:651, in
 install_sql_hook.<locals>.execute(self, sql, params)
     642 with record_sql_queries(
     643     cursor=self.cursor,
     644     query=sql,
    (...)    648     span_origin=DjangoIntegration.origin_db,
     649 ) as span:
     650     _set_db_data(span, self)
 --> 651     result = real_execute(self, sql, params)
     653 with capture_internal_exceptions():
     654     add_query_source(span)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/backends/utils.py:79, in CursorWrapper.execute(self,
 sql, params)
      78 def execute(self, sql, params=None):
 ---> 79     return self._execute_with_wrappers(
      80         sql, params, many=False, executor=self._execute
      81     )

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/backends/utils.py:92, in
 CursorWrapper._execute_with_wrappers(self, sql, params, many, executor)
      90 for wrapper in reversed(self.db.execute_wrappers):
      91     executor = functools.partial(wrapper, executor)
 ---> 92 return executor(sql, params, many, context)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/backends/utils.py:100, in CursorWrapper._execute(self,
 sql, params, *ignored_wrapper_args)
      98     warnings.warn(self.APPS_NOT_READY_WARNING_MSG,
 category=RuntimeWarning)
      99 self.db.validate_no_broken_transaction()
 --> 100 with self.db.wrap_database_errors:
     101     if params is None:
     102         # params default might be backend specific.
     103         return self.cursor.execute(sql)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/utils.py:91, in DatabaseErrorWrapper.__exit__(self,
 exc_type, exc_value, traceback)
      89 if dj_exc_type not in (DataError, IntegrityError):
      90     self.wrapper.errors_occurred = True
 ---> 91 raise dj_exc_value.with_traceback(traceback) from exc_value

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/django/db/backends/utils.py:105, in CursorWrapper._execute(self,
 sql, params, *ignored_wrapper_args)
     103     return self.cursor.execute(sql)
     104 else:
 --> 105     return self.cursor.execute(sql, params)

 File ~/.local/share/virtualenvs/registrar-D123KGkv/lib/python3.13/site-
 packages/psycopg/cursor.py:97, in Cursor.execute(self, query, params,
 prepare, binary)
      93         self._conn.wait(
      94             self._execute_gen(query, params, prepare=prepare,
 binary=binary)
      95         )
      96 except e._NO_TRACEBACK as ex:
 ---> 97     raise ex.with_traceback(None)
      98 return self

 InternalError: parse error - invalid geometry
 LINE 1: ...LL,NULL,NULL,NULL,NULL,NULL,NULL}')::tsvector[], ('{NULL,NUL...
                                                              ^
 HINT:  "NU" <-- parse error at position 2 within geometry
 }}}
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36347>
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/010701965f96ac54-f653b0a2-ef0e-465b-a016-b465f9bd4e08-000000%40eu-central-1.amazonses.com.

Reply via email to