Opened 11 months ago
Closed 10 months ago
#36034 closed Bug (fixed)
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: | Ready for checkin | |
| Has patch: | yes | 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 (4)
comment:1 by , 11 months ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:2 by , 10 months ago
| Has patch: | set |
|---|
comment:3 by , 10 months ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Note:
See TracTickets
for help on using tickets.
PR