Skip to content

Conversation

alcaeus
Copy link
Member

@alcaeus alcaeus commented Sep 18, 2025

Q A
Type bug
BC Break no
Fixed issues Fixes #2825, #2550, #2531, #2212

Summary

This fixes an issue when preparing elements for the query builder. The issue was that when $prepareValue in DocumentPersister::prepareQueryElement was true, the method would prepare identifier values and such, but not convert actual field values. Instead, this was done later in prepareQueryOrNewObj. However, that class then didn't contain the same dot logic contained in prepareQueryElement, leading to incomplete preparation of values.

This fix

@alcaeus alcaeus requested review from GromNaN and Copilot September 18, 2025 20:27
Copy link

@Copilot 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

This PR fixes a bug in nested query value preparation by ensuring that dot syntax field names are properly converted to database values during query preparation. The issue was that prepareQueryElement would handle nested field name mapping but skip database value conversion when $prepareValue was true, leaving this conversion to prepareQueryOrNewObj which lacked the dot syntax logic.

  • Moved database value conversion logic directly into prepareQueryElement to handle nested fields properly
  • Simplified prepareQueryOrNewObj to use array functions instead of manual loop for combining prepared elements
  • Added comprehensive test coverage for nested field preparation scenarios

Reviewed Changes

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

File Description
lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php Fixed nested query value preparation by moving database conversion into prepareQueryElement and updating parameter handling
tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php Added test case and supporting document classes to verify nested field preparation works correctly

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copilot

This comment was marked as resolved.

@alcaeus alcaeus added this to the 2.12.2 milestone Sep 19, 2025
Copy link
Member

@GromNaN GromNaN left a comment

Choose a reason for hiding this comment

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

Perfect!

@alcaeus
Copy link
Member Author

alcaeus commented Sep 22, 2025

Note: there are some cases that still aren't covered by this pull requests. I'm currently producing test cases for these cases to fix them properly.

@alcaeus alcaeus marked this pull request as draft September 22, 2025 11:10
['association.nested', 'associationName.nestedName'],
['association.nested.$id', 'associationName.nestedName.$id'],
['association.nested._id', 'associationName.nestedName._id'],
['association.nested.id', 'associationName.nestedName._id'],
Copy link
Member Author

Choose a reason for hiding this comment

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

These tests needed changing, as the field name translation only worked by accident. The nested property in the association document has no targetDocument, so we can't reliably know how to handle field names. The old logic reused the current ClassMetadata instance in these cases, which just happened to be the correct one in the tests, but might as well be incorrect. For example, this is the reason why an id property in an embedded document mapped without a targetDocument was renamed to _id in queries, even when it shouldn't have to.

Instead of changing the test documents to have a targetDocument, I figured it would be more prudent to highlight this change here. If reviewers feel that this change is a bit too risky for a patch release or even a minor version, please speak now or forever hold your peace.

Copy link
Member

Choose a reason for hiding this comment

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

What is a upgrade path if someone hits this breaking change?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good question, and the answer isn't straightforward. First, I should clarify that the instances where the ODM did field name translation, it likely didn't use the correct mappings, unless somebody had an A -> X -> A relationship. In all other cases, mappings wouldn't line up.

The problem only appeared in embeds without a target document, so the obvious solution would be to set a target document. If there is no common ancestor to the possible embedded documents, users would never be able to rely on mappings for queries anyways and would have to use field name translation themselves, i.e. write the field names as they appear in the database.

Copy link
Member

Choose a reason for hiding this comment

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

OK. I propose to target the minor version 2.13 instead of bugfix 2.12.2 and call it a feature.

Copy link
Member

@jmikola jmikola Sep 26, 2025

Choose a reason for hiding this comment

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

I concur with targeting 2.13 instead of 2.12.2.

The problem only appeared in embeds without a target document

Is there a legitimate use case for using EmbedMany or EmbedOne without a targetDocument mapping? When persisting to BSON, I assume ODM can use the class name to lookup metadata, but what happens when hydrating back from BSON? There'd be no hint for class metadata.

Perhaps this is a case where we should emit a warning/notice advising users to always specify targetDocument (even if it's a mapped superclass).

Copy link
Member Author

Choose a reason for hiding this comment

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

No use case that I know of, but I'd have to check if specifying a discriminatorMap in the mapping without a targetDocument will work.

@alcaeus alcaeus requested a review from GromNaN September 23, 2025 12:52
@alcaeus alcaeus marked this pull request as ready for review September 23, 2025 12:52
@alcaeus alcaeus requested a review from Copilot September 23, 2025 12:52
Copy link

@Copilot 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

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@alcaeus alcaeus force-pushed the support-dot-syntax-prepare-values branch from 261449c to 4d8875d Compare September 23, 2025 12:55
@GromNaN GromNaN modified the milestones: 2.12.2, 2.13.0 Sep 24, 2025
@GromNaN GromNaN requested a review from jmikola September 24, 2025 09:39
@alcaeus alcaeus force-pushed the support-dot-syntax-prepare-values branch from 4d8875d to 2a4b27b Compare September 24, 2025 13:40
@alcaeus alcaeus changed the base branch from 2.12.x to 2.13.x September 24, 2025 13:40
Comment on lines +860 to +866
self::assertArrayHasKey('embedded.embedded.test', $filter);
self::assertInstanceOf(ObjectId::class, $filter['embedded.embedded.test']);

self::assertArrayHasKey('embedded.embedded.embedded', $newObj['$set']);
self::assertIsArray($newObj['$set']['embedded.embedded.embedded']);
self::assertArrayHasKey('test', $newObj['$set']['embedded.embedded.embedded']);
self::assertInstanceOf(ObjectId::class, $newObj['$set']['embedded.embedded.embedded']['test']);
Copy link
Member

@GromNaN GromNaN Sep 29, 2025

Choose a reason for hiding this comment

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

I find the test easier to read with array comparison, but that we need an intermediate comparator to assert the same type and value. As $objectId == $oidString

        self::assertSameValueAndtype(['embedded.embedded.test' => $objectId], $filter);
        self::assertSameValueAndtype(['$set' => ['embedded.embedded.embedded' => ['test' => $objectId]]], $newObj);

@alcaeus alcaeus force-pushed the support-dot-syntax-prepare-values branch from 2a4b27b to b579078 Compare September 30, 2025 12:24
@alcaeus alcaeus requested a review from jmikola September 30, 2025 12:39
@alcaeus alcaeus force-pushed the support-dot-syntax-prepare-values branch from b579078 to 4490b91 Compare September 30, 2025 13:29
@alcaeus alcaeus force-pushed the support-dot-syntax-prepare-values branch from 4490b91 to 7620b56 Compare October 1, 2025 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment