Cascade delete: a restriction referencing an earlier-deleted (downstream) table is silently invalidated by reverse-topological delete order — materialize (independent of MySQL 1093)
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 4/5
- Tempo estimado
- 3-5 dias
- Facilidade para iniciantes
- 48/100
Direção de pesquisa
Rastreie o loop de exclusão em ordem topológica reversa em table.py:1089, inicialize o armazenamento de restrições em diagram.py:365 e a propagação de restrições em torno de diagram.py:1084 e _propagate_part_to_master. Revise condition.py:438-456 e :474 para entender as referências a restrições compiladas e adicione cobertura de paridade para os casos Part→Master, Part-of-Part e downstream-seed. A tarefa estará concluída quando o modo de exclusão materializar as restrições que fazem referência a tabelas excluídas anteriormente, enquanto os modos de visualização e exportação não fizerem isso.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Summary
A cascade delete whose restriction references a table that will be deleted earlier in the cascade
is silently invalidated by DataJoint's reverse-topological delete order — and this is independent
of MySQL error 1093. Table.delete deletes descendants (leaves) first and the seed last; if a
restriction references a table deleted before its own, that reference evaluates to empty by the
time its DELETE runs, so the row is stranded or mis-deleted. The fix is to materialize such
restrictions to literal keys before any deletion. MySQL 1093 is only an incidental, partial symptom
(see "Backend symptoms") — not the cause, and it must not shape the fix.
Root cause (backend-independent)
Table.delete executes one DELETE per table in reverse-topological order (table.py:1089), leaves
first, seed last. Restrictions are evaluated live at each table's delete time. Two kinds of
restriction reference a table deleted earlier:
- User seed restriction referencing a descendant.
(A & (X & cond)).delete()withXdownstream
ofA:X(descendant) is deleted first; thenA's DELETE re-evaluatesA & (X & cond), but the
matchingXrows are gone →Amatches nothing →Astranded while itsXchildren were
deleted. - Engine Part→Master upward reference. The Master's restriction is derived from its Part; the Part
(descendant) is deleted first → the Master would strand. Already materialized today in
_propagate_part_to_master.
Both are the same bug — a restriction referencing a table deleted before its own — on both backends.
Backend symptoms (secondary — NOT the framing)
In the sub-case where forward propagation makes a table's restriction reference itself, the
generated DELETE contains a self-referential subquery. MySQL rejects it with error 1093 and aborts
(an incidental, loud, partial backstop); PostgreSQL permits it and fails silently. But 1093 only
covers the self-referential subset — the seed-stranding case (#1: A's DELETE references X, not A)
does not trigger 1093 on either backend and is silent wherever it isn't materialized. So 1093 is
neither necessary nor sufficient to describe the problem.
Recommended approach
Materialize any cascade restriction that references a table deleted before it (a descendant in
delete order), before executing deletes. One backend-independent, 1093-free rule; it unifies the seed
case (#1) and the Part→Master case (#2).
- Detection (practical): text-search each table's compiled restriction for the
fully-qualified, quoted name of any earlier-deleted table in the cascade set. DataJoint emits
canonical qualified names, so this is reliable for engine-generated SQL; the action is materialize,
not reject, so false positives cost only an unnecessaryfetch('KEY'), never a wrong result.
User-authored raw SQL with non-canonical names is the advanced user's responsibility.- Detecting only self-reference (a table's own name) is INSUFFICIENT — it catches the 1093
sub-case but MISSES the seed (whose restriction names a descendant, not itself). The detection
target is "references an earlier-deleted table," not "references itself." - Simplest conservative variant: always materialize the seed restriction in delete mode (plus the
existing Part→Master materialization). One extrafetchof the keys being deleted; uniformly
correct. Detection merely avoids that fetch for simple restrictions that reference nothing downstream.
- Detecting only self-reference (a table's own name) is INSUFFICIENT — it catches the 1093
- Unifying the Part→Master special-case (
extract_master/_propagate_part_to_master) under this single
rule is a larger v2.4 refactor; a targeted 2.3.x fix can add seed-restriction materialization
first (the currently-unhandled case).
Delete vs. non-delete mode (materialize flag)
- Delete mode: materialize per the rule above.
- Non-delete mode (preview
counts(), data export): materialize nothing. The ordering hazard
exists only when rows are deleted; a preview/export issues SELECTs, which evaluate against current
data (and self-referential SELECTs are legal on both backends — 1093 is DML-only). Removes today's
wasted preview-time materialization (review F3) — a speedup.
Preview/delete divergence risk
Materialize from the same restricted expression the preview counts (restricted_T.fetch('KEY') vs
len(restricted_T)) so the affected set is identical by construction. Residual divergence is only
(a) concurrent data change between preview and delete (inherent to any preview-then-act split), and
(b) implementation drift between the count and materialize paths → mitigate with a shared builder and
parity tests across the tricky topologies (Part→Master, Part-of-Part, downstream-seed ref).
How it surfaces today
- Simple downstream semijoin seed (
A & (X & cond)) trips an unrelated plan-time crash first —
extract_column_names(condition.py:474) harvests the subquery's backtick identifiers (schema,
table) as if columns →Attribute \` is not found`. So the semijoin case fails-closed by
accident (cryptically) rather than stranding. - More complex conditions that pass planning: MySQL may abort via 1093 (self-ref sub-case); PostgreSQL
may silently strand. Backend-dependent patchwork.
References
- Surfaced in the 2.3.1
dj.Diagramreview (finding F8). - Code:
table.py:1089(reverse-topo delete loop),diagram.py:365(seed restriction storage),
diagram.py:1084(__reversed__→_restricted_table),_propagate_part_to_master(Part→Master
materialization),condition.py:438-456/:474(restriction rendering /extract_column_names).
- Linguagem predominante
- Python
- Estrelas
- 197
- Forks
- 98
- Merge médio
- 23h 9min
- PRs com merge (30d)
- 6
Preparar o ambiente
- Inclui um Dockerfile ou arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de datajoint/datajoint-python
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
datajoint/datajoint-python#1539 · 3 comentários ·
-
Python 3.15 ships Oct 9 and we cap below it; 3.10 went EOL Oct 1Talvez já em andamento @dimitri-yatsenko assumiu hoje. Abertaenhancement
datajoint/datajoint-python#1569 · 1 responsável ·
-
bug
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 75/100
datajoint/datajoint-python#1564 ·
-
bug
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 68/100
datajoint/datajoint-python#1563 ·
-
enhancement
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 35/100
datajoint/datajoint-python#1562 · 2 comentários ·
Todas as issues de datajoint/datajoint-python
Issues semelhantes
-
bug needs-triage
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
debpalash/VoiceStudio#2624 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
-
Make Catch2 optional when `RDK_BUILD_CPP_TESTS=OFF`Talvez já em andamento @pechersky assumiu hoje. Abertabug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 2 dias
-
There are a few redundant calls to `fdesc._setCloseOnExec()`Talvez já em andamento @gudnimg assumiu hoje. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 1 dia