@@ -1864,4 +1864,289 @@ public function testSchemalessNestedObjectAttributeQueries(): void
18641864
18651865 $ database ->deleteCollection ($ col );
18661866 }
1867+
1868+ public function testUpsertFieldRemoval (): void
1869+ {
1870+ /** @var Database $database */
1871+ $ database = $ this ->getDatabase ();
1872+
1873+ if ($ database ->getAdapter ()->getSupportForAttributes ()) {
1874+ $ this ->markTestSkipped ('Adapter supports attributes (schemaful mode). Field removal in upsert is tested in schemaful tests. ' );
1875+ }
1876+
1877+ $ collectionName = ID ::unique ();
1878+ $ database ->createCollection ($ collectionName , permissions: [
1879+ Permission::create (Role::any ()),
1880+ Permission::read (Role::any ()),
1881+ Permission::update (Role::any ()),
1882+ Permission::delete (Role::any ()),
1883+ ]);
1884+
1885+ $ permissions = [
1886+ Permission::read (Role::any ()),
1887+ Permission::create (Role::any ()),
1888+ Permission::update (Role::any ()),
1889+ Permission::delete (Role::any ()),
1890+ ];
1891+
1892+ // Test 1: Basic field removal with upsertDocument
1893+ // Create a document with multiple fields
1894+ $ doc1 = $ database ->createDocument ($ collectionName , new Document ([
1895+ '$id ' => 'doc1 ' ,
1896+ '$permissions ' => $ permissions ,
1897+ 'title ' => 'Original Title ' ,
1898+ 'description ' => 'Original Description ' ,
1899+ 'category ' => 'tech ' ,
1900+ 'tags ' => ['php ' , 'mongodb ' ],
1901+ 'metadata ' => [
1902+ 'author ' => 'John Doe ' ,
1903+ 'version ' => 1
1904+ ]
1905+ ]));
1906+
1907+ $ this ->assertEquals ('Original Title ' , $ doc1 ->getAttribute ('title ' ));
1908+ $ this ->assertEquals ('Original Description ' , $ doc1 ->getAttribute ('description ' ));
1909+ $ this ->assertEquals ('tech ' , $ doc1 ->getAttribute ('category ' ));
1910+ $ this ->assertArrayHasKey ('tags ' , $ doc1 ->getArrayCopy ());
1911+ $ this ->assertArrayHasKey ('metadata ' , $ doc1 ->getArrayCopy ());
1912+
1913+ // Upsert with fewer fields - removed fields should be deleted
1914+ $ upserted = $ database ->upsertDocument ($ collectionName , new Document ([
1915+ '$id ' => 'doc1 ' ,
1916+ '$permissions ' => $ permissions ,
1917+ 'title ' => 'Updated Title ' ,
1918+ 'category ' => 'science ' ,
1919+ // description, tags, and metadata are removed
1920+ ]));
1921+
1922+ $ this ->assertEquals ('Updated Title ' , $ upserted ->getAttribute ('title ' ));
1923+ $ this ->assertEquals ('science ' , $ upserted ->getAttribute ('category ' ));
1924+
1925+ // Verify removed fields are actually deleted
1926+ $ retrieved = $ database ->getDocument ($ collectionName , 'doc1 ' );
1927+ $ this ->assertEquals ('Updated Title ' , $ retrieved ->getAttribute ('title ' ));
1928+ $ this ->assertEquals ('science ' , $ retrieved ->getAttribute ('category ' ));
1929+ $ this ->assertArrayNotHasKey ('description ' , $ retrieved ->getArrayCopy ());
1930+ $ this ->assertArrayNotHasKey ('tags ' , $ retrieved ->getArrayCopy ());
1931+ $ this ->assertArrayNotHasKey ('metadata ' , $ retrieved ->getArrayCopy ());
1932+
1933+ // Test 2: Remove all custom fields except one
1934+ $ doc2 = $ database ->createDocument ($ collectionName , new Document ([
1935+ '$id ' => 'doc2 ' ,
1936+ '$permissions ' => $ permissions ,
1937+ 'field1 ' => 'value1 ' ,
1938+ 'field2 ' => 'value2 ' ,
1939+ 'field3 ' => 'value3 ' ,
1940+ 'field4 ' => 'value4 ' ,
1941+ ]));
1942+
1943+ // Upsert keeping only field1
1944+ $ database ->upsertDocument ($ collectionName , new Document ([
1945+ '$id ' => 'doc2 ' ,
1946+ '$permissions ' => $ permissions ,
1947+ 'field1 ' => 'updated_value1 ' ,
1948+ ]));
1949+
1950+ $ retrieved2 = $ database ->getDocument ($ collectionName , 'doc2 ' );
1951+ $ this ->assertEquals ('updated_value1 ' , $ retrieved2 ->getAttribute ('field1 ' ));
1952+ $ this ->assertArrayNotHasKey ('field2 ' , $ retrieved2 ->getArrayCopy ());
1953+ $ this ->assertArrayNotHasKey ('field3 ' , $ retrieved2 ->getArrayCopy ());
1954+ $ this ->assertArrayNotHasKey ('field4 ' , $ retrieved2 ->getArrayCopy ());
1955+
1956+ // Test 3: Remove nested object fields
1957+ $ doc3 = $ database ->createDocument ($ collectionName , new Document ([
1958+ '$id ' => 'doc3 ' ,
1959+ '$permissions ' => $ permissions ,
1960+ 'name ' => 'Product ' ,
1961+ 'details ' => [
1962+ 'color ' => 'red ' ,
1963+ 'size ' => 'large ' ,
1964+ 'weight ' => 10
1965+ ],
1966+ 'specs ' => [
1967+ 'cpu ' => 'Intel ' ,
1968+ 'ram ' => '8GB '
1969+ ]
1970+ ]));
1971+
1972+ // Upsert removing details but keeping specs
1973+ $ database ->upsertDocument ($ collectionName , new Document ([
1974+ '$id ' => 'doc3 ' ,
1975+ '$permissions ' => $ permissions ,
1976+ 'name ' => 'Updated Product ' ,
1977+ 'specs ' => [
1978+ 'cpu ' => 'AMD ' ,
1979+ 'ram ' => '16GB '
1980+ ],
1981+ // details is removed
1982+ ]));
1983+
1984+ $ retrieved3 = $ database ->getDocument ($ collectionName , 'doc3 ' );
1985+ $ this ->assertEquals ('Updated Product ' , $ retrieved3 ->getAttribute ('name ' ));
1986+ $ this ->assertArrayHasKey ('specs ' , $ retrieved3 ->getArrayCopy ());
1987+ $ this ->assertEquals ('AMD ' , $ retrieved3 ->getAttribute ('specs ' )['cpu ' ]);
1988+ $ this ->assertArrayNotHasKey ('details ' , $ retrieved3 ->getArrayCopy ());
1989+
1990+ // Test 4: Remove array fields
1991+ $ doc4 = $ database ->createDocument ($ collectionName , new Document ([
1992+ '$id ' => 'doc4 ' ,
1993+ '$permissions ' => $ permissions ,
1994+ 'title ' => 'Article ' ,
1995+ 'tags ' => ['tag1 ' , 'tag2 ' , 'tag3 ' ],
1996+ 'categories ' => ['cat1 ' , 'cat2 ' ],
1997+ 'comments ' => ['comment1 ' , 'comment2 ' ]
1998+ ]));
1999+
2000+ // Upsert removing tags and comments but keeping categories
2001+ $ database ->upsertDocument ($ collectionName , new Document ([
2002+ '$id ' => 'doc4 ' ,
2003+ '$permissions ' => $ permissions ,
2004+ 'title ' => 'Updated Article ' ,
2005+ 'categories ' => ['cat3 ' ],
2006+ ]));
2007+
2008+ $ retrieved4 = $ database ->getDocument ($ collectionName , 'doc4 ' );
2009+ $ this ->assertEquals ('Updated Article ' , $ retrieved4 ->getAttribute ('title ' ));
2010+ $ this ->assertArrayHasKey ('categories ' , $ retrieved4 ->getArrayCopy ());
2011+ $ this ->assertEquals (['cat3 ' ], $ retrieved4 ->getAttribute ('categories ' ));
2012+ $ this ->assertArrayNotHasKey ('tags ' , $ retrieved4 ->getArrayCopy ());
2013+ $ this ->assertArrayNotHasKey ('comments ' , $ retrieved4 ->getArrayCopy ());
2014+
2015+ // Test 5: upsertDocuments with field removal (bulk upsert)
2016+ $ docs5 = [
2017+ new Document ([
2018+ '$id ' => 'bulk1 ' ,
2019+ '$permissions ' => $ permissions ,
2020+ 'fieldA ' => 'valueA ' ,
2021+ 'fieldB ' => 'valueB ' ,
2022+ 'fieldC ' => 'valueC ' ,
2023+ ]),
2024+ new Document ([
2025+ '$id ' => 'bulk2 ' ,
2026+ '$permissions ' => $ permissions ,
2027+ 'fieldX ' => 'valueX ' ,
2028+ 'fieldY ' => 'valueY ' ,
2029+ 'fieldZ ' => 'valueZ ' ,
2030+ ]),
2031+ ];
2032+ $ database ->createDocuments ($ collectionName , $ docs5 );
2033+
2034+ // Upsert removing some fields from each
2035+ $ upsertDocs5 = [
2036+ new Document ([
2037+ '$id ' => 'bulk1 ' ,
2038+ '$permissions ' => $ permissions ,
2039+ 'fieldA ' => 'updatedA ' ,
2040+ // fieldB and fieldC removed
2041+ ]),
2042+ new Document ([
2043+ '$id ' => 'bulk2 ' ,
2044+ '$permissions ' => $ permissions ,
2045+ 'fieldX ' => 'updatedX ' ,
2046+ 'fieldZ ' => 'updatedZ ' ,
2047+ // fieldY removed
2048+ ]),
2049+ ];
2050+ $ database ->upsertDocuments ($ collectionName , $ upsertDocs5 );
2051+
2052+ $ retrievedBulk1 = $ database ->getDocument ($ collectionName , 'bulk1 ' );
2053+ $ this ->assertEquals ('updatedA ' , $ retrievedBulk1 ->getAttribute ('fieldA ' ));
2054+ $ this ->assertArrayNotHasKey ('fieldB ' , $ retrievedBulk1 ->getArrayCopy ());
2055+ $ this ->assertArrayNotHasKey ('fieldC ' , $ retrievedBulk1 ->getArrayCopy ());
2056+
2057+ $ retrievedBulk2 = $ database ->getDocument ($ collectionName , 'bulk2 ' );
2058+ $ this ->assertEquals ('updatedX ' , $ retrievedBulk2 ->getAttribute ('fieldX ' ));
2059+ $ this ->assertEquals ('updatedZ ' , $ retrievedBulk2 ->getAttribute ('fieldZ ' ));
2060+ $ this ->assertArrayNotHasKey ('fieldY ' , $ retrievedBulk2 ->getArrayCopy ());
2061+
2062+ // Test 6: Upsert creating new document (should not unset anything)
2063+ $ newDoc = $ database ->upsertDocument ($ collectionName , new Document ([
2064+ '$id ' => 'newDoc ' ,
2065+ '$permissions ' => $ permissions ,
2066+ 'newField ' => 'newValue ' ,
2067+ ]));
2068+
2069+ $ this ->assertEquals ('newValue ' , $ newDoc ->getAttribute ('newField ' ));
2070+ $ retrievedNew = $ database ->getDocument ($ collectionName , 'newDoc ' );
2071+ $ this ->assertEquals ('newValue ' , $ retrievedNew ->getAttribute ('newField ' ));
2072+ $ this ->assertArrayHasKey ('newField ' , $ retrievedNew ->getArrayCopy ());
2073+
2074+ // Test 7: Remove all custom fields (keep only system fields)
2075+ $ doc7 = $ database ->createDocument ($ collectionName , new Document ([
2076+ '$id ' => 'doc7 ' ,
2077+ '$permissions ' => $ permissions ,
2078+ 'custom1 ' => 'value1 ' ,
2079+ 'custom2 ' => 'value2 ' ,
2080+ 'custom3 ' => 'value3 ' ,
2081+ ]));
2082+
2083+ // Upsert with only system fields (no custom fields)
2084+ $ database ->upsertDocument ($ collectionName , new Document ([
2085+ '$id ' => 'doc7 ' ,
2086+ '$permissions ' => $ permissions ,
2087+ // No custom fields
2088+ ]));
2089+
2090+ $ retrieved7 = $ database ->getDocument ($ collectionName , 'doc7 ' );
2091+ $ this ->assertArrayNotHasKey ('custom1 ' , $ retrieved7 ->getArrayCopy ());
2092+ $ this ->assertArrayNotHasKey ('custom2 ' , $ retrieved7 ->getArrayCopy ());
2093+ $ this ->assertArrayNotHasKey ('custom3 ' , $ retrieved7 ->getArrayCopy ());
2094+ // System fields should still exist
2095+ $ this ->assertEquals ('doc7 ' , $ retrieved7 ->getId ());
2096+ $ this ->assertNotNull ($ retrieved7 ->getCreatedAt ());
2097+ $ this ->assertNotNull ($ retrieved7 ->getUpdatedAt ());
2098+
2099+ // Test 8: Mixed scenario - add new fields while removing others
2100+ $ doc8 = $ database ->createDocument ($ collectionName , new Document ([
2101+ '$id ' => 'doc8 ' ,
2102+ '$permissions ' => $ permissions ,
2103+ 'oldField1 ' => 'old1 ' ,
2104+ 'oldField2 ' => 'old2 ' ,
2105+ 'keepField ' => 'keep ' ,
2106+ ]));
2107+
2108+ // Upsert removing oldField1 and oldField2, keeping keepField, adding newField
2109+ $ database ->upsertDocument ($ collectionName , new Document ([
2110+ '$id ' => 'doc8 ' ,
2111+ '$permissions ' => $ permissions ,
2112+ 'keepField ' => 'updatedKeep ' ,
2113+ 'newField ' => 'newValue ' ,
2114+ ]));
2115+
2116+ $ retrieved8 = $ database ->getDocument ($ collectionName , 'doc8 ' );
2117+ $ this ->assertEquals ('updatedKeep ' , $ retrieved8 ->getAttribute ('keepField ' ));
2118+ $ this ->assertEquals ('newValue ' , $ retrieved8 ->getAttribute ('newField ' ));
2119+ $ this ->assertArrayNotHasKey ('oldField1 ' , $ retrieved8 ->getArrayCopy ());
2120+ $ this ->assertArrayNotHasKey ('oldField2 ' , $ retrieved8 ->getArrayCopy ());
2121+
2122+ // Test 9: Verify internal/system fields are never removed
2123+ $ doc9 = $ database ->createDocument ($ collectionName , new Document ([
2124+ '$id ' => 'doc9 ' ,
2125+ '$permissions ' => $ permissions ,
2126+ 'data ' => 'test ' ,
2127+ ]));
2128+
2129+ $ originalCreatedAt = $ doc9 ->getCreatedAt ();
2130+ $ originalUpdatedAt = $ doc9 ->getUpdatedAt ();
2131+
2132+ // Upsert - internal fields should be preserved
2133+ $ database ->upsertDocument ($ collectionName , new Document ([
2134+ '$id ' => 'doc9 ' ,
2135+ '$permissions ' => $ permissions ,
2136+ 'newData ' => 'newTest ' ,
2137+ ]));
2138+
2139+ $ retrieved9 = $ database ->getDocument ($ collectionName , 'doc9 ' );
2140+ // System fields should still exist
2141+ $ this ->assertEquals ('doc9 ' , $ retrieved9 ->getId ());
2142+ $ this ->assertEquals ($ originalCreatedAt , $ retrieved9 ->getCreatedAt ());
2143+ // UpdatedAt should be different (document was updated)
2144+ $ this ->assertNotEquals ($ originalUpdatedAt , $ retrieved9 ->getUpdatedAt ());
2145+ $ this ->assertEquals ('newTest ' , $ retrieved9 ->getAttribute ('newData ' ));
2146+ // Old field should be removed
2147+ $ this ->assertArrayNotHasKey ('data ' , $ retrieved9 ->getArrayCopy ());
2148+
2149+ // Clean up
2150+ $ database ->deleteCollection ($ collectionName );
2151+ }
18672152}
0 commit comments