Opened 7 hours ago

#36034 assigned Bug

ForeignKey to CompositePrimaryKey crashes.

Reported by: Mariusz Felisiak Owned by: Mariusz Felisiak
Component: Database layer (models, ORM) Version: dev
Severity: Release blocker Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I've created a sample project that tries to create a foreign key to the table with CompositePrimaryKey:

class Release(models.Model):
    pk = models.CompositePrimaryKey("version", "name")
    version = models.IntegerField()
    name = models.CharField(max_length=20)


class RefRelease(models.Model):
    release = models.ForeignKey("Release", models.CASCADE)

It doesn't work (because #35956 is not implemented):

$ python manage.py sqlmigrate test_one 0001
BEGIN;
--
-- Create model Release
--
CREATE TABLE "test_one_release" ("version" integer NOT NULL, "name" varchar(20) NOT NULL, PRIMARY KEY ("version", "name"));
--
-- Create model RefRelease
--
CREATE TABLE "test_one_refrelease" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT);
CREATE INDEX "test_one_refrelease_release_id_f24095be" ON "test_one_refrelease" ("release_id");
COMMIT;

FOREIGN KEY has not been created in "test_one_refrelease":

$ python manage.py dbshell
sqlite> pragma table_info(test_one_refrelease);
0|id|INTEGER|1||1
sqlite> pragma table_info(test_one_release);
0|version|INTEGER|1||1
1|name|varchar(20)|1||2
sqlite>

and any attempt to create a new object crashes:

$ python manage.py shell
>>> from test_one.models import *
>>> r =  Release.objects.create(version=3, name="y")
>>> RefRelease.objects.create(release=r)
Traceback (most recent call last):
  File "/django/django/db/backends/utils.py", line 105, in _execute
    return self.cursor.execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/backends/sqlite3/base.py", line 360, in execute
    return super().execute(query, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.OperationalError: table test_one_refrelease has no column named release_id

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

Traceback (most recent call last):
  File "/django/django/db/backends/utils.py", line 134, in debug_sql
    yield
  File "/django/django/db/backends/utils.py", line 122, in execute
    return super().execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/backends/utils.py", line 79, in execute
    return self._execute_with_wrappers(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/backends/utils.py", line 92, in _execute_with_wrappers
    return executor(sql, params, many, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/backends/utils.py", line 100, in _execute
    with self.db.wrap_database_errors:
  File "/django/django/db/utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/django/django/db/backends/utils.py", line 105, in _execute
    return self.cursor.execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/backends/sqlite3/base.py", line 360, in execute
    return super().execute(query, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
django.db.utils.OperationalError: table test_one_refrelease has no column named release_id

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/django/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/models/query.py", line 663, in create
    obj.save(force_insert=True, using=self.db)
  File "/django/django/db/models/base.py", line 901, in save
    self.save_base(
  File "/django/django/db/models/base.py", line 1007, in save_base
    updated = self._save_table(
              ^^^^^^^^^^^^^^^^^
  File "/django/django/db/models/base.py", line 1170, in _save_table
    results = self._do_insert(
              ^^^^^^^^^^^^^^^^
  File "/django/django/db/models/base.py", line 1211, in _do_insert
    return manager._insert(
           ^^^^^^^^^^^^^^^^
  File "/django/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/models/query.py", line 1849, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/models/sql/compiler.py", line 1891, in execute_sql
    cursor.execute(sql, params)
  File "/django/django/db/backends/utils.py", line 121, in execute
    with self.debug_sql(sql, params, use_last_executed_query=True):
  File "/usr/local/lib/python3.12/contextlib.py", line 158, in __exit__
    self.gen.throw(value)
  File "/django/django/db/backends/utils.py", line 139, in debug_sql
    sql = self.db.ops.last_executed_query(self.cursor, sql, params)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/backends/sqlite3/operations.py", line 178, in last_executed_query
    params = self._quote_params_for_last_executed_query(params)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/django/django/db/backends/sqlite3/operations.py", line 167, in _quote_params_for_last_executed_query
    return cursor.execute(sql, params).fetchone()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.ProgrammingError: Error binding parameter 1: type 'tuple' is not supported

IMO, we should at least raise an error (system check) in such cases. For now, migrations are proceeding without any indication that anything went wrong.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top