Repository navigation
fix(relationships): ignore relationships whose related collection was deleted - #1034
Merged
Merged
Conversation
… deleted 7.x deleteCollection() could leave relationship attributes behind on other collections, pointing at a collection whose metadata no longer exists. 7.x getCollection() returned an empty document for it, so document operations effectively skipped such a relationship. 8.x getCollection() throws NotFound, so every get, find, create, update, upsert and delete on a collection holding one fails with "Collection not found". The relationship hook now resolves a relationship's related collection with findCollection() and treats a missing one as a plain attribute: it is not related, populated, authorized, cascaded or restricted, and its stored value is returned as is. A write keeps the ID on the side holding the key and drops the value from a side that stores nothing. Population resolves the related collection once per relationship and passes it to the batch loaders, instead of reading it in each loader and again before queueing the next level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 27 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Production incident
Cloud 1.51.0 (database 8.3.0):
PATCH /v1/tablesdb/{db}/tables/{t}/rows/{rowId}returns 500Utopia\Database\Exception\NotFound: Collection not found(Trait/Collections.php:203,getCollection()) for a project whose collections hold one-way many-to-one relationship attributes (child side, onDelete setNull / restrict) whose related collection was deleted on 7.x. The related collection's metadata is gone; the relationship attribute is not.7.4.2's
getCollection()returned an empty document for a missing collection, so an untouched relationship was skipped. 8.x'sgetCollection()throws, and the relationship hook calls it for every relationship on every document operation, so any get, find, create, update, upsert or delete on such a collection fails.Fix
The relationship hook resolves a relationship's related collection through one helper,
relatedCollection(), built on the non-throwingfindCollection(). When the related collection is missing, the relationship is treated as a plain attribute:nullincluded; a nested document is stored by its ID). A side that stores nothing (one-to-many parent, many-to-one child, many-to-many) drops the value. Untouched values are left alone.Documents::validateRelationshipSelects()also skips the select validation for a related collection that is missing.Population now resolves the related collection once per relationship and passes it to the batch loaders. Before, each loader read it, and then it was read again before queueing the next level. Relationship filter queries that reach into a deleted collection (
author.name = x) still throw, because there is no schema to query against.Compared with 7.4.2: an untouched or null orphaned relationship worked there, and still works here. A non-null write threw
Collection not foundthere (relateDocumentsById()→getDocument('', …)). Here it stores the raw ID.Regression tests
tests/e2e/Adapter/Scopes/Relationships/OrphanedTests.php, part ofRelationshipTests, runs on every adapter that supports relationships. The fixture buildsarticleswith:author(setNull) andeditor(restrict), held by articlescomments(setNull) andlikes(restrict), held by other collections (the production shape)tags(cascade) and manyToManytopics(restrict)It then drops each related collection's table and deletes its
_metadatarow, the state 7.x left.testOrphanedRelationshipUpdateDocument(the production failure; partial, whole-document, raw-ID, null and stores-nothing writes)testOrphanedRelationshipCreateDocumenttestOrphanedRelationshipGetDocument(with and withoutauthor.*select)testOrphanedRelationshipFind(no select,select(name),select(name, author.*),select(*, author.name, comments.name, tags.name, topics.name),equal(author))testOrphanedRelationshipDeleteDocument(deleteDocument+deleteDocuments)testOrphanedRelationshipUpsertDocument(update and insert)testOrphanedRelationshipUpdateDocumentsBefore the fix: 83 of 84 adapter×test runs failed with the production exception. The 84th was the Memory upsert test, which skips. After the fix: 84 of 84 pass.
Local verification
composer lint(Pint): passedcomposer check(PHPStan, both configs): no errorsRelationshipTests,Relationships/*,JoinRelationshipTests, 219 tests each) are green on SQLite, MariaDB, MySQL, Postgres, Memory and SharedTables/MySQL.🤖 Generated with Claude Code