@@ -7456,6 +7456,54 @@ public function testRegexInjection(): void
74567456 $ database ->deleteCollection ($ collectionName );
74577457 }
74587458
7459+ public function testUpdateDocumentUsesFreshForUpdateReadWhenCacheIsStale (): void
7460+ {
7461+ /** @var Database $database */
7462+ $ database = $ this ->getDatabase ();
7463+
7464+ $ collectionId = 'for_update_cache ' ;
7465+ $ database ->createCollection ($ collectionId );
7466+
7467+ if ($ database ->getAdapter ()->getSupportForAttributes ()) {
7468+ $ this ->assertEquals (true , $ database ->createAttribute ($ collectionId , 'a ' , Database::VAR_STRING , 255 , false ));
7469+ $ this ->assertEquals (true , $ database ->createAttribute ($ collectionId , 'b ' , Database::VAR_STRING , 255 , false ));
7470+ }
7471+
7472+ $ database ->createDocument ($ collectionId , new Document ([
7473+ '$id ' => 'doc1 ' ,
7474+ '$permissions ' => [
7475+ Permission::read (Role::any ()),
7476+ Permission::update (Role::any ()),
7477+ ],
7478+ 'a ' => 'A1 ' ,
7479+ 'b ' => 'B1 ' ,
7480+ ]));
7481+
7482+ // Prime cache with initial values.
7483+ $ cached = $ database ->getDocument ($ collectionId , 'doc1 ' );
7484+ $ this ->assertEquals ('B1 ' , $ cached ->getAttribute ('b ' ));
7485+
7486+ $ collection = $ database ->getCollection ($ collectionId );
7487+
7488+ // Simulate an out-of-band write that bypasses cache invalidation.
7489+ $ outOfBand = $ database ->getAdapter ()->getDocument ($ collection , 'doc1 ' );
7490+ $ outOfBand ->setAttribute ('b ' , 'B2 ' );
7491+ $ database ->getAdapter ()->updateDocument ($ collection , 'doc1 ' , $ outOfBand , true );
7492+
7493+ // Partial update should not overwrite untouched fields with stale cached values.
7494+ $ updated = $ database ->updateDocument ($ collectionId , 'doc1 ' , new Document ([
7495+ 'a ' => 'A2 ' ,
7496+ ]));
7497+
7498+ $ this ->assertEquals ('A2 ' , $ updated ->getAttribute ('a ' ));
7499+ $ this ->assertEquals ('B2 ' , $ updated ->getAttribute ('b ' ));
7500+
7501+ $ fresh = $ database ->getDocument ($ collectionId , 'doc1 ' );
7502+ $ this ->assertEquals ('B2 ' , $ fresh ->getAttribute ('b ' ));
7503+
7504+ $ database ->deleteCollection ($ collectionId );
7505+ }
7506+
74597507 /**
74607508 * Test ReDoS (Regular Expression Denial of Service) with timeout protection
74617509 * This test verifies that ReDoS patterns either timeout properly or complete quickly,
0 commit comments