#36034: ForeignKey to CompositePrimaryKey crashes.
-------------------------------------+-------------------------------------
Reporter: Mariusz | Owner: Mariusz Felisiak
Felisiak |
Type: Bug | Status: assigned
Component: Database | Version: dev
layer (models, ORM) |
Severity: Release | Keywords:
blocker |
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
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.
--
Ticket URL: <
https://code.djangoproject.com/ticket/36034>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.