Follow-ups to #1080: batch subject removal, self-loop deletion, test hardening#1111
Merged
alistair3149 merged 2 commits intoJul 20, 2026
Merged
Conversation
Follow-up to #1080. - removeAbsentSubjects and deletePage now route through a single removeSubjects() that classifies referenced-vs-unreferenced subjects in one query and deletes the unreferenced ones in one query, instead of one round trip per subject. Both call sites stay unified on the same stub/delete logic. - subjectIdsWithIncomingRelations excludes self-loops, so a subject whose only incoming relation is its own self-reference is deleted rather than left as an unreachable orphan stub. - reduceSubjectToStub collapses the OPTIONAL MATCH cartesian product with WITH DISTINCT so the property reset runs once, not once per outgoing relation. - getSubjectIdsByPageId no longer fetches the node properties and labels that both callers discard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to #1080. - testFlippingASubjectBetweenMainAndChildLeavesASingleHasSubjectEdge locks detachSubjectsFromPage: without it, flipping a subject's isMain flag leaves a duplicate HasSubject edge (verified red when the call is removed). - testRemovingASelfReferencingSubjectDeletesItRatherThanStubbingIt guards the self-loop deletion. - assertRelationExists now optionally asserts the preserved relation's id, so a regression that keeps an incoming relation by endpoints and type but drops its id is caught. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
alistair3149
force-pushed
the
review/1080-followups
branch
from
July 20, 2026 22:26
b676731 to
75a64de
Compare
alistair3149
marked this pull request as ready for review
July 20, 2026 22:28
alistair3149
merged commit Jul 20, 2026
a5f1c3a
into
fix/544-referenced-subject-stubs
6 checks passed
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.
Addresses the review findings on #1080. Targets the PR branch (
fix/544-referenced-subject-stubs), not master, so it can be folded into that PR before it merges.What each commit does
Batch subject removal —
src/GraphDatabasePlugins/Neo4j/Persistence/Neo4jProjectionStore.phpremoveAbsentSubjectsanddeletePagenow route through oneremoveSubjects(), which classifies referenced-vs-unreferenced subjects in a single query and deletes the unreferenced ones in a single query, replacing the per-subject round-trip loop. Both call sites stay unified on the same stub/delete logic (the point of the fix in Keep referenced Subjects as stubs instead of deleting them #1080), so this does not re-introduce asavePage/deletePagesplit.other <> subject), so a subject whose only incoming relation is its own self-reference is deleted rather than left as an unreachable orphan stub.reduceSubjectToStubgetsWITH DISTINCT subjectso the property/label reset runs once, not once per outgoing relation (the twoOPTIONAL MATCHes form a cartesian product).getSubjectIdsByPageIdstops returning the node properties and label list that both callers discarded.Test hardening —
tests/…/Neo4jProjectionStoreTest.phptestFlippingASubjectBetweenMainAndChildLeavesASingleHasSubjectEdgelocksdetachSubjectsFromPage, which had no coverage. BecauseHasSubjectis MERGEd with itsisMainproperty, flipping a persisting subject's role would leave a duplicate edge without that method. Teeth-verified: the test goes red when thedetachSubjectsFromPagecall is removed.testRemovingASelfReferencingSubjectDeletesItRatherThanStubbingIt— verified red on the pre-fix code (a self-loop subject was kept as a stub), green after.assertRelationExistsoptionally asserts the preserved relation'sid, so a regression that keeps an incoming relation by endpoints and type but drops itsidis caught. Pinned at the four preservation call sites.Behavioural note for the reviewer
Batching the classification changes one acknowledged-limitation edge: removing two mutually-referencing subjects in one save now leaves both as orphan stubs (deterministic) instead of one stub + one deleted (order-dependent). Both outcomes are within the acknowledged, harmless, self-healing orphan-stub limitation from #1080; the proper cascade-sweep fix is deferred and tracked in #1112. Flagging it because it is a minor semantic shift.
Validation
Run locally against the dev stack (
@group Database):Neo4jProjectionStore34 ✓,Neo4jSubjectRelationUpdater8 ✓ReplaceSubjectApi✓,Neo4jPageIdentifiersLookup✓,Neo4jSubjectLabelLookup✓,MediaWikiPageDeleter✓make cs(phpcs 579 files + phpstan) ✓deletePageon a referenced subject reduced it to a correct stub with its incoming relations preserved; a full graph rebuild restored the projection to baseline.Draft because the full suite was not run locally; CI is the gate for the remainder.