Skip to content

Comments

fix: handle BackLink types in fetch_link and fetch_all_links#1286

Open
roman-right wants to merge 1 commit intomainfrom
fix/issue-885-backlink-fetch-all
Open

fix: handle BackLink types in fetch_link and fetch_all_links#1286
roman-right wants to merge 1 commit intomainfrom
fix/issue-885-backlink-fetch-all

Conversation

@roman-right
Copy link
Member

Problem

fetch_link() and fetch_all_links() silently skip BackLink fields. When a user calls doc.fetch_link("back_link") or doc.fetch_all_links() on a document with BackLink fields, those fields remain as unresolved BackLink objects instead of being populated with the actual linked documents.

Fix

Added handling for BACK_DIRECT, OPTIONAL_BACK_DIRECT, BACK_LIST, and OPTIONAL_BACK_LIST link types in fetch_link(). For single back-links, query the reverse collection with {lookup_field_name}.$id: self.id} and set the result. For list back-links, query and collect all matching documents. fetch_all_links() already delegates to fetch_link() per field, so it works automatically.

Closes #885

@roman-right roman-right requested a review from Copilot February 17, 2026 00:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes fetch_link()/fetch_all_links() so BackLink fields are actually resolved instead of being silently skipped, addressing #885.

Changes:

  • Added BackLink resolution logic to Document.fetch_link() for direct and list back-links.
  • Added regression tests covering fetch_link() for single/list BackLinks and fetch_all_links() behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
tests/odm/test_relations.py Adds regression tests ensuring BackLink fields are populated by fetch_link()/fetch_all_links().
beanie/odm/documents.py Implements BackLink fetching by querying the reverse collection using the lookup field $id.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1194 to +1195
query = {f"{link_info.lookup_field_name}.$id": self.id}
result = await link_info.document_class.find_one(query)
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

BackLink queries currently match only on ...$id == self.id, but DBRef-like storage is typically {field: DBRef(collection, id)} and may include $ref/$db parts. Matching only $id can produce false positives if the same id value exists for different referenced collections (or if ids are not globally unique across collections). Consider querying on the whole DBRef (e.g., {lookup_field_name: self.to_ref()}) or including $ref in the predicate (e.g., {f\"{lookup_field_name}.$ref\": <collection>, f\"{lookup_field_name}.$id\": self.id}) to ensure the reverse lookup is collection-safe.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

The query already targets a specific collection via link_info.document_class, so cross-collection false positives are not possible. This is the same {lookup_field_name}.$id}pattern used in all existing$lookupstages inbeanie/odm/utils/find.py`.

Comment on lines +1206 to +1208
results = await link_info.document_class.find(
{f"{link_info.lookup_field_name}.$id": self.id},
).to_list()
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

Same issue as the BACK_DIRECT case: filtering only on ...$id can match references from the wrong collection. Prefer querying by full DBRef (or at least $ref + $id) when resolving BACK_LIST/OPTIONAL_BACK_LIST to avoid incorrect BackLink populations.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above. The collection is already scoped by document_class.

Comment on lines +808 to +809
await back_link_doc.fetch_link("back_link")
assert len(back_link_doc.back_link) == 1
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

For list BackLinks, the order of find(...).to_list() is unspecified unless a sort is applied. If multiple documents can match in real scenarios, this test (and the implementation) may become flaky or hide ordering expectations. Consider either asserting set membership/ids without relying on ordering, or updating the implementation to apply a deterministic sort (and then assert the order in the test).

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

The test creates exactly one matching document, so ordering is not relevant here. The assertion checks length and id, not position.

@roman-right
Copy link
Member Author

Related: #1285 (BackLink values reverting after save). Both PRs fix BackLink field handling -- #1285 prevents merge_models from overwriting fetched BackLinks, this PR makes fetch_link/fetch_all_links populate them in the first place.

Copy link
Member

@staticxterm staticxterm left a comment

Choose a reason for hiding this comment

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

Regarding this When a user calls doc.fetch_link("back_link") or doc.fetch_all_links() on a document with BackLink fields, those fields remain as unresolved BackLink objects instead of being populated with the actual linked documents., what if doing so results in an endless loop if the referenced BackLink also includes a reference to the "current" document from which we are fetching the "BackLink"?

):
if self.id is None:
return
query = {f"{link_info.lookup_field_name}.$id": self.id}
Copy link
Member

Choose a reason for hiding this comment

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

Not related to this PR but I believe this should be investigated and hopefully fixed with a follow-up PR.
There is one open issue on this matter: #1131
And I've also verified this myself that the index is not being used but instead a full collection scan is being made.
Also, why does the Link.fetch_list() utilize a different query on the "_id" field?

@staticxterm staticxterm requested a review from a team February 22, 2026 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Backlinks are not populated

2 participants