Skip to content

fix(queryset): use subquery for DELETE/UPDATE filtering by related fields - #2139

Open
noy-solvin wants to merge 12 commits into
tortoise:developfrom
noy-solvin:fix/queryset
Open

fix(queryset): use subquery for DELETE/UPDATE filtering by related fields#2139
noy-solvin wants to merge 12 commits into
tortoise:developfrom
noy-solvin:fix/queryset

Conversation

@noy-solvin

Copy link
Copy Markdown

Description

Modified DeleteQuery._make_query() and UpdateQuery._make_query() in tortoise/queryset.py to use a subquery pattern - WHERE id IN (SELECT id FROM (SELECT id FROM table JOIN ... WHERE ...) AS _t).
Also preserved LIMIT and ORDER BY clauses within the internal subquery.

Motivation and Context

DELETE and UPDATE queries were failing when filtering by related fields (foreign keys) because the engine was trying to use JOINs, which MySQL and SQLite don't support for these operations.
closes #283

How Has This Been Tested?

Added test_delete_filter_with_foreign_key and test_update_filter_with_foreign_key to the test suite.
Verified that the full regression suite (1899 tests) passes. The fix was also tested manually.

Full transparency: this fix was generated using Solvin, an AI coding agent my team is building. Reviewed and tested manually before submitting. I'd love your feedback. The fix was fully tested manually by me prior to submitting this PR.

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added the changelog accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@codspeed-hq

codspeed-hq Bot commented Mar 12, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing noy-solvin:fix/queryset (4727f84) with develop (4aa3790)

Open in CodSpeed

Comment thread tortoise/queryset.py Outdated
Comment thread tortoise/queryset.py Outdated
Comment thread tests/test_queryset.py

@themavik themavik left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changes — the implementation is clean and follows the existing patterns.

Comment thread tests/test_queryset.py
Comment thread tests/test_queryset.py
@noy-solvin

Copy link
Copy Markdown
Author

🔍 The Problem

The test cases test_delete_filter_with_foreign_key and test_update_filter_with_foreign_key in tests/test_queryset.py lacked negative control records. Without these negative controls, the tests were unable to verify that delete and update operations only affect target records, leaving other database entries unaffected.

🛠️ The Solution

  • Added author2 and book3 as negative control records in test_delete_filter_with_foreign_key to verify that deleting books by one author does not affect books belonging to other authors.

  • Modified the assertions in test_delete_filter_with_foreign_key to verify that exactly one book remains in the database after deletion.

  • Added author2 and book2 as negative control records in test_update_filter_with_foreign_key to verify that updating books by one author does not modify the ratings of other books.

  • Modified the assertions in test_update_filter_with_foreign_key to check that the targeted book rating was updated to 1.0 while the control book rating remained untouched at 5.0.

🟢 Confidence: High

📊 Solvin Autonomous Verification Matrix

Engineering Dimension Status / Score Technical Telemetry
🎯 Intent Clarity 🟢 High The input issue description clearly defines the missing negative controls and specifies the exact functions in tests/test_queryset.py.
🔍 RCA Confidence 🟢 High Root cause isolated with high confidence to missing negative control records in foreign key delete and update test cases.
🛠️ Execution Safety 🟢 High Compiler safety and execution of unit and full regression test suites passed successfully with zero errors.
🗺️ Code Blast Radius 🟢 Low (Indicates high containment / safe footprint) The change footprint is extremely small and isolated entirely within unit tests, resulting in zero blast radius.
🧠 Fact & Logic Grounding 🟢 High LLM Judge audit verified complete grounding with zero hallucinations across all execution records.

The overall confidence is High as the changes are extremely well-defined and fully verified. The Code Blast Radius is Low because the changes are entirely isolated within test cases and do not affect the core application codebase.

✅ Verification

  • Executed unit tests specifically for tests/test_queryset.py under Unit mode, with all 73 tests (65 passed, 8 skipped, 0 failed) passing successfully.

  • Executed full regression testing check, confirming that all 1936 tests passed with zero compilation errors, zero suite errors, and zero regressions.

  • Solvin completed architectural and code reviews confirming that the updated tests follow best practices and do not violate any design patterns.

  • A security regression scan confirmed the new code has no security issue.

@noy-solvin
noy-solvin requested a review from waketzheng June 16, 2026 11:14
@noy-solvin

Copy link
Copy Markdown
Author

Hi @waketzheng
Would love you feedback!
Thank you

Comment thread CHANGELOG.rst Outdated
@noy-solvin

Copy link
Copy Markdown
Author

Thanks @waketzheng for reviewing! I made the necessary change.

@noy-solvin
noy-solvin requested a review from waketzheng June 25, 2026 11:11
@noy-solvin

Copy link
Copy Markdown
Author

Hi @waketzheng, I would love your feedback.
Thanks!

@waketzheng

Copy link
Copy Markdown
Contributor

@noy-solvin, could you merge upstream/develop again?

@waketzheng

Copy link
Copy Markdown
Contributor

Additionally, please take a look at abondar's review comment. Also, after adding your code, the _make_query function has become a bit too long — as a rule, a function should not exceed one screen in length.

@noy-solvin

Copy link
Copy Markdown
Author

Thanks for the reply. I have addressed the issues :) @waketzheng

Comment thread tests/test_queryset.py
author2 = await Author.create(name="test2")
await Book.create(name="book3", author=author2, rating=5.0)

# This is the failing query

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this comment line.

Comment thread CHANGELOG.rst

Added
^^^^^
- Tests for model validators. (#2137)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this, as it is unrelated to this PR.

Comment thread tests/test_queryset.py


def test_update_query_postgres_dialect_coverage(db):
from tortoise.backends.base.client import Capabilities

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't need to be inside a function — please move it to the top of the file.

Comment thread tests/test_queryset.py


def test_delete_query_postgres_dialect_coverage(db):
from tortoise.backends.base.client import Capabilities

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also move it to the top.

Comment thread tests/test_queryset.py


@pytest.mark.asyncio
async def test_delete_limit_order_by_with_join(db):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are this test and the one above related to this PR? If not, please move them to a separate PR. If they are related, please use a TestClass to reduce duplicated code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delete with related field query fails due to invalid SQL generation

5 participants