@@ -1685,4 +1685,129 @@ public function testElemMatchComplex(): void
16851685 // Clean up
16861686 $ database ->deleteCollection ($ collectionId );
16871687 }
1688+
1689+ public function testSchemalessNestedObjectAttributeQueries (): void
1690+ {
1691+ /** @var Database $database */
1692+ $ database = static ::getDatabase ();
1693+
1694+ // Only run for schemaless adapters that support object attributes
1695+ if ($ database ->getAdapter ()->getSupportForAttributes () || !$ database ->getAdapter ()->getSupportForObject ()) {
1696+ $ this ->expectNotToPerformAssertions ();
1697+ return ;
1698+ }
1699+
1700+ $ col = uniqid ('sl_nested_obj ' );
1701+ $ database ->createCollection ($ col );
1702+
1703+ $ permissions = [
1704+ Permission::read (Role::any ()),
1705+ Permission::write (Role::any ()),
1706+ Permission::update (Role::any ()),
1707+ Permission::delete (Role::any ())
1708+ ];
1709+
1710+ // Documents with nested objects
1711+ $ database ->createDocument ($ col , new Document ([
1712+ '$id ' => 'u1 ' ,
1713+ '$permissions ' => $ permissions ,
1714+ 'profile ' => [
1715+ 'name ' => 'Alice ' ,
1716+ 'location ' => [
1717+ 'country ' => 'US ' ,
1718+ 'city ' => 'New York ' ,
1719+ 'coordinates ' => [
1720+ 'lat ' => 40.7128 ,
1721+ 'lng ' => -74.0060 ,
1722+ ],
1723+ ],
1724+ ],
1725+ ]));
1726+
1727+ $ database ->createDocument ($ col , new Document ([
1728+ '$id ' => 'u2 ' ,
1729+ '$permissions ' => $ permissions ,
1730+ 'profile ' => [
1731+ 'name ' => 'Bob ' ,
1732+ 'location ' => [
1733+ 'country ' => 'UK ' ,
1734+ 'city ' => 'London ' ,
1735+ 'coordinates ' => [
1736+ 'lat ' => 51.5074 ,
1737+ 'lng ' => -0.1278 ,
1738+ ],
1739+ ],
1740+ ],
1741+ ]));
1742+
1743+ // Document without full nesting
1744+ $ database ->createDocument ($ col , new Document ([
1745+ '$id ' => 'u3 ' ,
1746+ '$permissions ' => $ permissions ,
1747+ 'profile ' => [
1748+ 'name ' => 'Charlie ' ,
1749+ 'location ' => [
1750+ 'country ' => 'US ' ,
1751+ ],
1752+ ],
1753+ ]));
1754+
1755+ // Query using Mongo-style dotted paths: attribute.key.key
1756+ $ nycDocs = $ database ->find ($ col , [
1757+ Query::equal ('profile.location.city ' , ['New York ' ]),
1758+ ]);
1759+ $ this ->assertCount (1 , $ nycDocs );
1760+ $ this ->assertEquals ('u1 ' , $ nycDocs [0 ]->getId ());
1761+
1762+ // Query on deeper nested numeric field
1763+ $ northOf50 = $ database ->find ($ col , [
1764+ Query::greaterThan ('profile.location.coordinates.lat ' , 50 ),
1765+ ]);
1766+ $ this ->assertCount (1 , $ northOf50 );
1767+ $ this ->assertEquals ('u2 ' , $ northOf50 [0 ]->getId ());
1768+
1769+ // exists on nested key should match docs where the full path exists
1770+ $ withCoordinates = $ database ->find ($ col , [
1771+ Query::exists (['profile.location.coordinates.lng ' ]),
1772+ ]);
1773+ $ this ->assertCount (2 , $ withCoordinates );
1774+ $ ids = array_map (fn (Document $ doc ) => $ doc ->getId (), $ withCoordinates );
1775+ $ this ->assertContains ('u1 ' , $ ids );
1776+ $ this ->assertContains ('u2 ' , $ ids );
1777+ $ this ->assertNotContains ('u3 ' , $ ids );
1778+
1779+ // Combination of filters on nested paths
1780+ $ usWithCoords = $ database ->find ($ col , [
1781+ Query::equal ('profile.location.country ' , ['US ' ]),
1782+ Query::exists (['profile.location.coordinates.lat ' ]),
1783+ ]);
1784+ $ this ->assertCount (1 , $ usWithCoords );
1785+ $ this ->assertEquals ('u1 ' , $ usWithCoords [0 ]->getId ());
1786+
1787+ // contains on object attribute using nested structure: parent.key and [key => [key => 'value']]
1788+ $ matchedByNestedContains = $ database ->find ($ col , [
1789+ Query::contains ('profile ' , [
1790+ 'location ' => [
1791+ 'city ' => 'London ' ,
1792+ ],
1793+ ]),
1794+ ]);
1795+ $ this ->assertCount (1 , $ matchedByNestedContains );
1796+ $ this ->assertEquals ('u2 ' , $ matchedByNestedContains [0 ]->getId ());
1797+
1798+ // equal on object attribute using nested structure should behave similarly
1799+ $ matchedByNestedEqual = $ database ->find ($ col , [
1800+ Query::equal ('profile ' , [
1801+ 'location ' => [
1802+ 'country ' => 'US ' ,
1803+ ],
1804+ ]),
1805+ ]);
1806+ $ this ->assertCount (2 , $ matchedByNestedEqual );
1807+ $ idsEqual = array_map (fn (Document $ doc ) => $ doc ->getId (), $ matchedByNestedEqual );
1808+ $ this ->assertContains ('u1 ' , $ idsEqual );
1809+ $ this ->assertContains ('u3 ' , $ idsEqual );
1810+
1811+ $ database ->deleteCollection ($ col );
1812+ }
16881813}
0 commit comments