Opened 7 years ago
Closed 7 years ago
#29569 closed Bug (fixed)
Cast() to AutoField generates invalid SQL
| Reported by: | Andrew Standley | Owned by: | Mariusz Felisiak |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | cast |
| Cc: | Mariusz Felisiak | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | yes | UI/UX: | no |
Description
Using PostgreSQL 10.1 I encountered a problem trying to use Cast with an AutoField. Trying to perform a manual join on generic foreign keys:
Item.objects.filter(pk__in=Activity.objects.filter(content_type=item_ct).annotate(casted_pk=Cast('object_pk', AutoField())).values('casted_pk'))
The db connection responds with a "psycopg2.ProgrammingError: type "serial" does not exist" error.
Upon investigation, it seems that AutoField.cast_db_type uses the inherited definition from Field and returns AutoField.db_type. However AutoField.db_type returns 'serial', which is a "syntactical sugar" (not a true type) in Postgresql and only valid for creation.
The issue arises when AutoField.cast_db_type also returns 'serial', which is invalid.
More confusingly AutoField.rel_db_type has already been overridden and correctly returns 'integer'.
I believe fixing this is as easy as copying the override of AutoField.rel_db_type to AutoField.cast_db_type.
I'll try to at least get this implemented as a test on my fork, so that it is easy to confirm, but in the mean time the models to reproduce are:
class Item(models.Model):
label = models.CharField(max_length=100)
cost = models.IntegerField()
class Activity(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_pk = models.CharField(max_length=100)
generic_object = GenericForeignKey(fk_field='object_pk')
Reference:
PostgreSQL docs on 'serial' type (https://www.postgresql.org/docs/10/static/datatype-numeric.html#DATATYPE-SERIAL)
Change History (7)
follow-up: 3 comment:1 by , 7 years ago
| Cc: | added |
|---|---|
| Easy pickings: | set |
| Summary: | AutoField.cast_db_type returns the wrong 'type' for PostgrestSQL → Cast() to the AutoField generates invalid SQL. |
| Version: | 2.0 → master |
comment:2 by , 7 years ago
| Summary: | Cast() to the AutoField generates invalid SQL. → Cast() to AutoField generates invalid SQL |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
follow-up: 4 comment:3 by , 7 years ago
I'm using Cast on generic foreign keys to handle the type coercion in the database, so I'm not coding around a 'known' field-type. I would indeed just use an IntegerField, 'integer' type directly otherwise, and as a temporary solution I'm doing just that for the edge case of AutoField.
Can you elaborate on how I would use DatabaseOperations.cast_data_types to fix this? I'm afraid I'm not familiar with that whole side of things. It (perhaps naively) seems to me that DatabaseOperations is going to have no effect on the Cast operation compilation as Cast selects the db_type through a different route than the one that DatabaseOperations.cast_data_type is used in.
Replying to felixxm:
Thanks for the report. It doesn't sound natural to me to cast to
AutoField, because if you want to get aninteger/bigint, why not use it directly? Nevertheless you can useDatabaseOperations.cast_data_typesto fix this easily. This issue probably appears on other back-ends.
comment:4 by , 7 years ago
In Django we defined DatabaseOperations for each back-end. cast_db_type() uses back-end specific dictionary connection.ops.cast_data_types to find data type to use in the Cast() (see django/db/models/fields/__init__.py).
comment:6 by , 7 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Thanks for the report. It doesn't sound natural to me to cast to
AutoField, because if you want to get aninteger/bigint, why not use it directly? Nevertheless you can useDatabaseOperations.cast_data_typesto fix this easily. This issue probably appears on other back-ends.