Repository navigation
fix(cascade): seed a delete with its keys when its restriction is another query, and keep a % in it (2.3) - #1612
Open
dimitri-yatsenko wants to merge 2 commits into
Open
dimitri-yatsenko wants to merge 2 commits into
dimitri-yatsenko wants to merge 2 commits into
Conversation
A delete restricted by another query, such as `(Subject & (Session & "quality = 0")).delete()`, failed. The cascade applies the seed's compiled conditions again to a fresh copy of the table through `restrict()`, which collects column names from the whole condition text, subquery included, and rejects every name the seed table lacks. The delete raised `Attribute ... is not found in query` before deleting anything. On MySQL the backquoted schema name in every subquery tripped the same check, so even `(Subject & Session).delete()` raised. On PostgreSQL, a seed whose subquery names only the seed's columns got past the check but still did nothing. The cascade deletes children before parents, so by the time it reached the seed, the rows its subquery reads were gone. `(Subject & Session).delete()` matched no subject, returned 0 and rolled back. A seed whose conditions contain a subquery is now replaced by the primary keys it selects, as a `dj.Top` seed already is. The traversal and the delete then work from the rows the restriction selected when the delete started. The new tests delete subjects restricted by a query on their sessions, by its negation, and by the session table itself. They check the preview counts and the rows left afterwards, on both backends. All six cases fail on the previous code. (cherry picked from commit 6863332)
dimitri-yatsenko
requested review from
MilagrosMarin,
lum-agilyti and
ttngu207
October 17, 2026 22:09
A delete restricted by a condition containing %, such as `(Label & "name = '50%'").delete()` or a dict `{"name": "50%"}`, deleted nothing and returned 0. The seed's conditions are compiled SQL, in which each % is already escaped as %% for the database driver. The cascade applies them again through `restrict()`, which escaped every % a second time, so the condition looked for the literal text `50%%` and matched no row. A LIKE pattern kept working only because `%%` and `%` match the same strings there.
The seed's conditions now have that first escape undone before the cascade applies them again, so they select the same rows as the restriction on its own.
The new test checks the preview counts for a string condition, a dict and a condition holding `%%`, and deletes with a LIKE pattern, on both backends. It fails before this change.
(cherry picked from commit e9e1d67)
This branch has not been deployed
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.
Backport of #1611 to
maint/2.3for v2.3.5, cherry-picked unchanged. Stacked on #1593, so its base isfix/cascade-overdelete-2.3; it moves tomaint/2.3when #1593 merges.A delete restricted by another query, such as
(Subject & (Session & "quality = 0")).delete(), failed. The cascadeapplies the seed's compiled conditions again to a fresh copy of the table
through
restrict(), which collects column names from the whole conditiontext, subquery included, and rejects every name the seed table lacks. The
delete raised
Attribute ... is not found in querybefore deletinganything. On MySQL the backquoted schema name in every subquery tripped the
same check, so even
(Subject & Session).delete()raised.On PostgreSQL, a seed whose subquery names only the seed's columns got past
the check but still did nothing. The cascade deletes children before
parents, so by the time it reached the seed, the rows its subquery reads
were gone.
(Subject & Session).delete()matched no subject, returned 0and rolled back.
A seed whose conditions contain a subquery is now replaced by the primary
keys it selects, as a
dj.Topseed already is. The traversal and thedelete then work from the rows the restriction selected when the delete
started.
The new tests delete subjects restricted by a query on their sessions, by
its negation, and by the session table itself. They check the preview
counts and the rows left afterwards, on both backends. All six cases fail
on the previous code.
Fixes #1610.
Second commit: keep a % in a delete's restriction (#1636)
A delete restricted by a condition containing %, such as
(Label & "name = '50%'").delete()or a dict{"name": "50%"}, deleted nothing and returned 0. The seed's conditions are compiled SQL, in which each % is already escaped as %%; the cascade applies them again throughrestrict(), which escaped every % a second time. The seed's conditions now have that first escape undone before the cascade applies them again. The new test checks a string condition, a dict and a condition holding%%, on both backends, and fails before this change.Fixes #1636.