Opened 2 years ago
Closed 2 years ago
#34945 closed Bug (duplicate)
annotate -> union -> values gives wrong values
| Reported by: | Tom Carrick | Owned by: | nobody |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 4.2 |
| Severity: | Normal | 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
With the following code:
import uuid
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.functions import Cast
class DocumentQuerySet(models.QuerySet):
def for_user(self, user):
return self.filter(things__user=user).union(
self.filter(other_things__user=user)
)
class Document(models.Model):
name = models.CharField()
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
objects = DocumentQuerySet().as_manager()
class Thing(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name="things", on_delete=models.CASCADE
)
document = models.ForeignKey(
Document, related_name="things", on_delete=models.CASCADE
)
class OtherThing(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name="other_things", on_delete=models.CASCADE
)
document = models.ForeignKey(
Document, related_name="other_things", on_delete=models.CASCADE
)
def broken():
user = get_user_model().objects.first()
return Document.objects.annotate(
pk_str=Cast("pk", output_field=models.CharField())
).for_user(user).values_list("pk_str", flat=True)
Running broken(), I would expect to say the stringified UUIDs from the Document model. Instead I get the names. It also happens when using values(). If not using either, it works just fine, and you can access document.pk_str and it works just fine. After playing with this, it seems to be because it's defined first in the model. If I move id to the top, it actually returns the UUIDs, without the cast being applied. The query (as far as I can tell from Django) looks fine:
(SELECT "testapp_document"."id" AS "col1", "testapp_document"."name" AS "col2", ("testapp_document"."id")::varchar AS "pk_str" FROM "testapp_document" INNER JOIN "testapp_thing" ON ("testapp_document"."id" = "testapp_thing"."document_id") WHERE "testapp_thing"."user_id" = 1) UNION (SELECT "testapp_document"."id" AS "col1", "testapp_document"."name" AS "col2", ("testapp_document"."id")::varchar AS "pk_str" FROM "testapp_document" INNER JOIN "testapp_otherthing" ON ("testapp_document"."id" = "testapp_otherthing"."document_id") WHERE "testapp_otherthing"."user_id" = 1)
And logged from Postgres also looks correct:
(SELECT "testapp_document"."name" AS "col1", "testapp_document"."id" AS "col2", ("testapp_document"."id")::varchar AS "pk_str" FROM "testapp_document" INNER JOIN "testapp_thing" ON ("testapp_document"."id" = "testapp_thing"."document_id") WHERE "testapp_thing"."user_id" = 1) UNION (SELECT "testapp_document"."name" AS "col1", "testapp_document"."id" AS "col2", ("testapp_document"."id")::varchar AS "pk_str" FROM "testapp_document" INNER JOIN "testapp_otherthing" ON ("testapp_document"."id" = "testapp_otherthing"."document_id") WHERE "testapp_otherthing"."user_id" = 1) LIMIT 21
Change History (3)
comment:1 by , 2 years ago
comment:2 by , 2 years ago
Oops! Actually I realised I was on 4.2, this seems to be working for me on dev, so it's more likely a duplicate of #28553.
But your version doesn't work on dev? Hard to say but they seem similar, the fix could be the same.
Oops, don't mind me, I was using the wrong test code for a minute.
It's indeed possible it's a duplicate of #28900 as you say, but hard to say for sure.
comment:3 by , 2 years ago
| Resolution: | → duplicate |
|---|---|
| Status: | new → closed |
Duplicate of #28900? 🤔
I've managed to boil this down the following example:
class Foo(Model): name = CharField() class Bar(Model): name = CharField() qs = ( Foo.objects.annotate(alias=F("name")) .union(Bar.objects.annotate(alias=F("name"))) .values("alias") ) print(qs)gives
<QuerySet [{'alias': 1}, {'alias': 1}]>