fix(queryset): support DELETE/UPDATE with filters on related fields - #2269
Merged
waketzheng merged 1 commit intoSep 1, 2026
Merged
Conversation
Filters on related fields add JOINs to the query, but JOINs are not valid in
DELETE/UPDATE statements on most backends, so e.g. on SQLite
LogMessage.filter(device__name="test").delete()
produces
DELETE FROM "logmessage" LEFT OUTER JOIN "device" ...
sqlite3.OperationalError: near "LEFT": syntax error
and the UPDATE path renders a broken aliased FROM clause on top of that.
When filters add joins, target the primary key through a subquery instead:
DELETE FROM "logmessage" WHERE "id" IN (
SELECT "_t"."id" FROM (
SELECT "logmessage"."id" FROM "logmessage" LEFT OUTER JOIN ...
) AS "_t"
)
The derived-table wrapper keeps MySQL (ER_UPDATE_TABLE_USED) happy, and the
non-JOIN path stays untouched.
Fixes tortoise#283
waketzheng
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
QuerySet.delete()/QuerySet.update()on a filter that touches a related field produce an invalid statement: the JOIN added byresolve_filters()is left in place when the query is turned into a DELETE/UPDATE.On SQLite:
The UPDATE path is also broken (separately): for a filter on a related field it renders an aliased
FROM "book" "book_"whose alias does not exist, e.g.no such column: book.author_id.Motivation and Context
Fixes #283. This is a normal, documented usage pattern (
filteracross a foreign key followed bydelete()/update()), and it has been broken on the default SQLite backend since the DELETE/UPDATE rewrite (see the discussion in #283).Solution
When the resolved filters add joins to the query, the target rows are selected by primary key through a subquery instead:
The derived-table wrapper keeps MySQL happy (
ER_UPDATE_TABLE_USED), and the non-JOIN path is left untouched, so simpledelete()/update()queries are unchanged.How Has This Been Tested?
tests/test_queryset.py(test_delete_filter_by_related_field,test_update_filter_by_related_field) — they fail withOperationalErrorbefore the fix and pass after it.source_fieldPK column, andlimit/order_byon MySQL): DELETE and UPDATE both execute and only the matching rows are affected.Checklist: