Skip to content

sqlite3.OperationalError: too many SQL variables during bulk_create_with_history #1216

@ntouran

Description

@ntouran

Describe the bug
Running bulk_create_with_history with just 200 50 objects causes the following exception when running on a sqlite3 backend.

This is more problematic for Sqlite3 < 3.32.0 (https://www.sqlite.org/limits.html) where the limit is less, but may still be a problem for larger bulk creations on later versions.

Traceback (most recent call last):
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/backends/sqlite3/base.py", line 423, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: too many SQL variables

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

Traceback (most recent call last):
  File "/srv/atom/live/documents/tests/test_doc_section_import.py", line 35, in test_import_doc_sections
    make_sections_from_xlsx(
  File "/srv/atom/live/documents/utils/make_sections_from_xlsx.py", line 89, in make_sections_from_xlsx
    docs = bulk_create_with_history(
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/simple_history/utils.py", line 132, in bulk_create_with_history
    list(
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/models/query.py", line 262, in __len__
    self._fetch_all()
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/models/query.py", line 1324, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/models/query.py", line 51, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql
    cursor.execute(sql, params)
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/srv/venvs/atom39/lib64/python3.9/site-packages/django/db/backends/sqlite3/base.py", line 423, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: too many SQL variables

To Reproduce
Steps to reproduce the behavior:

  1. Install sqlite3 < 3.32.0
  2. Bulk create 50 objects that have ~10 fields/columns each
  3. See error

Expected behavior
Bulk create should work in this scenario without failing.

Screenshots
If applicable, add screenshots to help explain your problem.

Environment (please complete the following information):

  • OS: RHEL 8
  • Browser (if applicable): N/A
  • Django Simple History Version: 3.3.0
  • Django Version: 3.2
  • Database Version: Sqlite3 3.26.0

Additional context
This problem started after upgrading to 3.3.0 from 3.1.0. It is coming from this commit 6325659 from #975, specifically by building up a very large SQL query while building the obj_when_list:

https://github.com/jazzband/django-simple-history/blob/a768673e0dc5e34e1a81481b0a0d3bfe09a38f8a/simple_history/utils.py#L132-L135

Through the debugger, I printed out that list and it has N When clauses (where N is 200 for creating 200 objects, each of which has M SQL commands where M is the number of fields in the model. This ends up making a SQL command that is N*M long, which seems pretty big. Maybe too big since users may want to bulk_create millions of rows at once, and this will eventually cause issues for any database backend.

@twolfson can you take a look at this by chance? Looks to me like having multiple SQL queries would be preferable to having fewer but unbounded large ones.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions