feat: add union query support#2146
Conversation
# Conflicts: # tortoise/queryset.py
| assert result == expected | ||
|
|
||
|
|
||
| @requireCapability(dialect=NotEQ("mssql")) |
There was a problem hiding this comment.
MSSql limit syntax is different but it's not currently supported in _SetOperation which has its own implementation of _limit_sql() instead of using the dialect SQL. I guess we can leave it as a TODO for later
|
@abondar I addressed your comments, can you please re-review this PR? |
| model._meta.app == row[app_field] | ||
| and model._meta._model.__name__ == row[model_field] | ||
| ): | ||
| instance_list.append(model._init_from_db(**row)) |
There was a problem hiding this comment.
I think it is not good idea to pass row like that, with extra values of app field and model field. I think we should strip them. I understand that it works now like that, but it seems fragile and can interfere with actual fields declared on model. Maybe even consider making these special field names more unique, to lower chance of overlapping with actual fields (maybt "_*" prefix?)
There was a problem hiding this comment.
I'm not sure what you mean. We add 2 annotation fields: tortoise_app and tortoise_model here:
https://github.com/seladb/tortoise-orm/blob/074a61e59e88bbe221aa835f7ba6aa8390e7a605/tortoise/queryset.py#L2316-L2319
I can change the field names to __tortoise_app__ and __tortoise_model__ if you think it's better, but there's always a (very small) chance it will interfere with existing fields.
I think we should strip them
What do you mean by that?
There was a problem hiding this comment.
You are passing these field values inside model init, I think we shouldn't do it, we should pop them from row dict
There was a problem hiding this comment.
Oh ok, it makes sense. Addressed in 9d541c2
| if getattr(select, "alias") not in [cls.TORTOISE_APP_FIELD, cls.TORTOISE_MODEL_FIELD] | ||
| ] | ||
|
|
||
| def _make_query(self) -> None: |
There was a problem hiding this comment.
I feel we still should make it more idempotent, maybe just add self._union_query = None at start?
tortoise/queryset.py
Outdated
| return list(dict(result[0]).values())[0] | ||
|
|
||
|
|
||
| class UnionQuery(AwaitableQuery[MODEL]): |
There was a problem hiding this comment.
I am skeptical about inheretence here - we inherit AwaitableQuery, but don't use many of it's fields, also redefining __slots__ and not setting those base fields in _clone
Is there reason why we want that inheretence at all? only for choose_db methods? Maybe we should extract choose_db methods to some mixing used by both?
There was a problem hiding this comment.
Do you want me to implement it in this PR or move it to a separate PR?
I'm a bit concerned that this PR already contains quite a lot of changes, maybe we should avoid making even more changes?
There was a problem hiding this comment.
Well, it's still change that concerns new class, it would be strange to ship it with bad inheritance model and start refactoring it right after
There was a problem hiding this comment.
I made this change in 5f4ad80, please let me know what you think
- Change `UnionQuery` to inherit from `_ChooseDBMixin` instead of `AwaitableQuery`
|
@abondar I addressed your PR comments, please let me know what you think |
|
Thank you @abondar for the thorough review and for merging this PR, much appreciated! 🙏 |
Add union query support in an API similar to Django's API
Addresses these issues:
#1507
#2056 (partially)
Description
Add union query support:
As opposed to Django, this API supports result of multiple models (in Django all results will be instances of the first model).
However, similar to Django, the requested field names and types should be the same across all queried models. This is because of how DBs support union queries.
Motivation and Context
It addresses the following issues:
#1507
#2056 (partially)
Also - it brings more feature parity with Django.
How Has This Been Tested?
Added test coverage for all written code. It was tested with MySQL, Postgres and SQLite.
Checklist: