@@ -1178,6 +1178,144 @@ public function testUpsertMixedPermissionDelta(): void
11781178 ], $ db ->getDocument (__FUNCTION__ , 'b ' )->getPermissions ());
11791179 }
11801180
1181+ public function testPreserveSequenceUpsert (): void
1182+ {
1183+ /** @var Database $database */
1184+ $ database = $ this ->getDatabase ();
1185+
1186+ if (!$ database ->getAdapter ()->getSupportForUpserts ()) {
1187+ $ this ->expectNotToPerformAssertions ();
1188+ return ;
1189+ }
1190+
1191+ $ collectionName = 'preserve_sequence_upsert ' ;
1192+
1193+ $ database ->createCollection ($ collectionName );
1194+
1195+ if ($ database ->getAdapter ()->getSupportForAttributes ()) {
1196+ $ database ->createAttribute ($ collectionName , 'name ' , Database::VAR_STRING , 128 , true );
1197+ }
1198+
1199+ // Create initial documents
1200+ $ doc1 = $ database ->createDocument ($ collectionName , new Document ([
1201+ '$id ' => 'doc1 ' ,
1202+ '$permissions ' => [
1203+ Permission::read (Role::any ()),
1204+ Permission::update (Role::any ()),
1205+ ],
1206+ 'name ' => 'Alice ' ,
1207+ ]));
1208+
1209+ $ doc2 = $ database ->createDocument ($ collectionName , new Document ([
1210+ '$id ' => 'doc2 ' ,
1211+ '$permissions ' => [
1212+ Permission::read (Role::any ()),
1213+ Permission::update (Role::any ()),
1214+ ],
1215+ 'name ' => 'Bob ' ,
1216+ ]));
1217+
1218+ $ originalSeq1 = $ doc1 ->getSequence ();
1219+ $ originalSeq2 = $ doc2 ->getSequence ();
1220+
1221+ $ this ->assertNotEmpty ($ originalSeq1 );
1222+ $ this ->assertNotEmpty ($ originalSeq2 );
1223+
1224+ // Test: Without preserveSequence (default), $sequence should be ignored
1225+ $ database ->setPreserveSequence (false );
1226+
1227+ $ database ->upsertDocuments ($ collectionName , [
1228+ new Document ([
1229+ '$id ' => 'doc1 ' ,
1230+ '$sequence ' => 999 , // Try to set a different sequence
1231+ '$permissions ' => [
1232+ Permission::read (Role::any ()),
1233+ Permission::update (Role::any ()),
1234+ ],
1235+ 'name ' => 'Alice Updated ' ,
1236+ ]),
1237+ ]);
1238+
1239+ $ doc1Updated = $ database ->getDocument ($ collectionName , 'doc1 ' );
1240+ $ this ->assertEquals ('Alice Updated ' , $ doc1Updated ->getAttribute ('name ' ));
1241+ $ this ->assertEquals ($ originalSeq1 , $ doc1Updated ->getSequence ()); // Sequence unchanged
1242+
1243+ // Test: With preserveSequence=true, $sequence from document should be used
1244+ $ database ->setPreserveSequence (true );
1245+
1246+ $ database ->upsertDocuments ($ collectionName , [
1247+ new Document ([
1248+ '$id ' => 'doc2 ' ,
1249+ '$sequence ' => $ originalSeq2 , // Keep original sequence
1250+ '$permissions ' => [
1251+ Permission::read (Role::any ()),
1252+ Permission::update (Role::any ()),
1253+ ],
1254+ 'name ' => 'Bob Updated ' ,
1255+ ]),
1256+ ]);
1257+
1258+ $ doc2Updated = $ database ->getDocument ($ collectionName , 'doc2 ' );
1259+ $ this ->assertEquals ('Bob Updated ' , $ doc2Updated ->getAttribute ('name ' ));
1260+ $ this ->assertEquals ($ originalSeq2 , $ doc2Updated ->getSequence ()); // Sequence preserved
1261+
1262+ // Test: withPreserveSequence helper
1263+ $ database ->setPreserveSequence (false );
1264+
1265+ $ doc1 = $ database ->getDocument ($ collectionName , 'doc1 ' );
1266+ $ currentSeq1 = $ doc1 ->getSequence ();
1267+
1268+ $ database ->withPreserveSequence (function () use ($ database , $ collectionName , $ currentSeq1 ) {
1269+ $ database ->upsertDocuments ($ collectionName , [
1270+ new Document ([
1271+ '$id ' => 'doc1 ' ,
1272+ '$sequence ' => $ currentSeq1 ,
1273+ '$permissions ' => [
1274+ Permission::read (Role::any ()),
1275+ Permission::update (Role::any ()),
1276+ ],
1277+ 'name ' => 'Alice Final ' ,
1278+ ]),
1279+ ]);
1280+ });
1281+
1282+ $ doc1Final = $ database ->getDocument ($ collectionName , 'doc1 ' );
1283+ $ this ->assertEquals ('Alice Final ' , $ doc1Final ->getAttribute ('name ' ));
1284+ $ this ->assertEquals ($ currentSeq1 , $ doc1Final ->getSequence ());
1285+
1286+ // Verify flag was reset after withPreserveSequence
1287+ $ this ->assertFalse ($ database ->getPreserveSequence ());
1288+
1289+ // Test: With preserveSequence=true, invalid $sequence should throw error (SQL adapters only)
1290+ $ database ->setPreserveSequence (true );
1291+
1292+ try {
1293+ $ database ->upsertDocuments ($ collectionName , [
1294+ new Document ([
1295+ '$id ' => 'doc1 ' ,
1296+ '$sequence ' => 'abc ' , // Invalid sequence value
1297+ '$permissions ' => [
1298+ Permission::read (Role::any ()),
1299+ Permission::update (Role::any ()),
1300+ ],
1301+ 'name ' => 'Alice Invalid ' ,
1302+ ]),
1303+ ]);
1304+ // Schemaless adapters may not validate sequence type, so only fail for schemaful
1305+ if ($ database ->getAdapter ()->getSupportForAttributes ()) {
1306+ $ this ->fail ('Expected StructureException for invalid sequence ' );
1307+ }
1308+ } catch (Throwable $ e ) {
1309+ if ($ database ->getAdapter ()->getSupportForAttributes ()) {
1310+ $ this ->assertInstanceOf (StructureException::class, $ e );
1311+ $ this ->assertStringContainsString ('sequence ' , $ e ->getMessage ());
1312+ }
1313+ }
1314+
1315+ $ database ->setPreserveSequence (false );
1316+ $ database ->deleteCollection ($ collectionName );
1317+ }
1318+
11811319 public function testRespectNulls (): Document
11821320 {
11831321 /** @var Database $database */
0 commit comments