Skip to content

Mixed model union queries behave like UNION ALL #2185

@misscatmint

Description

@misscatmint

Given the following models:

from tortoise import Model, fields

class Cat(Model):
    name = fields.CharField()

class Dog(Model):
    name = fields.CharField()

And the following rows:

await Cat.create(name='Charles')
await Dog.create(name='Charles')

Doing a select union on name will return two rows, as if all=True were specified:

cats = Cat.filter(name='Charles').only('name')
dogs = Dog.filter(name='Charles').only('name')
pets = await cats.union(dogs) # returns [Cat(name='Charles'), Dog(name='Charles')]

This happens because the model names are implicitly included in the SELECT query, which isn't obvious from the code alone. It also isn't entirely obvious what the code should do if it didn't do that (i.e., which model would it return for the one row).

I think supporting values()/values_list() for mixed model unions would make a lot of sense for the above case. Something like this:

cats = Cat.filter(name='Charles')
dogs = Dog.filter(name='Charles')
names = await cats.union(dogs).values('name') # returns ['Charles']

And maybe otherwise an error should be raised for mixed models if all=False and values() is not used?

Also, minor aside, but given the above example, the current implementation of union() also causes confusion with some type checkers such as basedpyright:

error: Argument of type "QuerySet[Dog]" cannot be assigned to parameter "other_qs" of type "QuerySet[Model]" in function "union"
    "QuerySet[Dog]" is not assignable to "QuerySet[Model]"
      Type parameter "MODEL@QuerySet" is invariant, but "Dog" is not the same as "Model" (reportArgumentType)

Maybe something like variadic generics could be used to fix this, but I'm not 100% sure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions