@@ -7722,4 +7722,200 @@ public function testRegexInjection(): void
77227722 // }
77237723 // $database->deleteCollection($collectionName);
77247724 // }
7725+
7726+ public function testCreateDocumentsIgnoreDuplicates (): void
7727+ {
7728+ /** @var Database $database */
7729+ $ database = $ this ->getDatabase ();
7730+
7731+ $ database ->createCollection (__FUNCTION__ );
7732+ $ database ->createAttribute (__FUNCTION__ , 'name ' , Database::VAR_STRING , 128 , true );
7733+
7734+ // Insert initial documents
7735+ $ database ->createDocuments (__FUNCTION__ , [
7736+ new Document ([
7737+ '$id ' => 'doc1 ' ,
7738+ 'name ' => 'Original A ' ,
7739+ '$permissions ' => [
7740+ Permission::read (Role::any ()),
7741+ Permission::create (Role::any ()),
7742+ ],
7743+ ]),
7744+ new Document ([
7745+ '$id ' => 'doc2 ' ,
7746+ 'name ' => 'Original B ' ,
7747+ '$permissions ' => [
7748+ Permission::read (Role::any ()),
7749+ Permission::create (Role::any ()),
7750+ ],
7751+ ]),
7752+ ]);
7753+
7754+ // Without ignore, duplicates should throw
7755+ try {
7756+ $ database ->createDocuments (__FUNCTION__ , [
7757+ new Document ([
7758+ '$id ' => 'doc1 ' ,
7759+ 'name ' => 'Duplicate A ' ,
7760+ '$permissions ' => [
7761+ Permission::read (Role::any ()),
7762+ Permission::create (Role::any ()),
7763+ ],
7764+ ]),
7765+ ]);
7766+ $ this ->fail ('Expected DuplicateException ' );
7767+ } catch (DuplicateException $ e ) {
7768+ $ this ->assertNotEmpty ($ e ->getMessage ());
7769+ }
7770+
7771+ // With ignore, duplicates should be silently skipped
7772+ $ emittedIds = [];
7773+ $ count = $ database ->createDocuments (__FUNCTION__ , [
7774+ new Document ([
7775+ '$id ' => 'doc1 ' ,
7776+ 'name ' => 'Duplicate A ' ,
7777+ '$permissions ' => [
7778+ Permission::read (Role::any ()),
7779+ Permission::create (Role::any ()),
7780+ ],
7781+ ]),
7782+ new Document ([
7783+ '$id ' => 'doc3 ' ,
7784+ 'name ' => 'New C ' ,
7785+ '$permissions ' => [
7786+ Permission::read (Role::any ()),
7787+ Permission::create (Role::any ()),
7788+ ],
7789+ ]),
7790+ ], onNext: function (Document $ doc ) use (&$ emittedIds ) {
7791+ $ emittedIds [] = $ doc ->getId ();
7792+ }, ignore: true );
7793+
7794+ // Only doc3 was new, doc1 was skipped as duplicate
7795+ $ this ->assertSame (1 , $ count );
7796+ $ this ->assertCount (1 , $ emittedIds );
7797+ $ this ->assertSame ('doc3 ' , $ emittedIds [0 ]);
7798+
7799+ // doc3 should exist, doc1 should retain original value
7800+ $ doc1 = $ database ->getDocument (__FUNCTION__ , 'doc1 ' );
7801+ $ this ->assertSame ('Original A ' , $ doc1 ->getAttribute ('name ' ));
7802+
7803+ $ doc3 = $ database ->getDocument (__FUNCTION__ , 'doc3 ' );
7804+ $ this ->assertSame ('New C ' , $ doc3 ->getAttribute ('name ' ));
7805+
7806+ // Total should be 3 (doc1, doc2, doc3)
7807+ $ all = $ database ->find (__FUNCTION__ );
7808+ $ this ->assertCount (3 , $ all );
7809+ }
7810+
7811+ public function testCreateDocumentsIgnoreIntraBatchDuplicates (): void
7812+ {
7813+ /** @var Database $database */
7814+ $ database = $ this ->getDatabase ();
7815+ $ col = 'createDocsIgnoreIntraBatch ' ;
7816+
7817+ $ database ->createCollection ($ col );
7818+ $ database ->createAttribute ($ col , 'name ' , Database::VAR_STRING , 128 , true );
7819+
7820+ // Two docs with same ID in one batch — first wins, second is deduplicated
7821+ $ emittedIds = [];
7822+ $ count = $ database ->createDocuments ($ col , [
7823+ new Document ([
7824+ '$id ' => 'dup ' ,
7825+ 'name ' => 'First ' ,
7826+ '$permissions ' => [
7827+ Permission::read (Role::any ()),
7828+ Permission::create (Role::any ()),
7829+ ],
7830+ ]),
7831+ new Document ([
7832+ '$id ' => 'dup ' ,
7833+ 'name ' => 'Second ' ,
7834+ '$permissions ' => [
7835+ Permission::read (Role::any ()),
7836+ Permission::create (Role::any ()),
7837+ Permission::update (Role::user ('extra ' )),
7838+ ],
7839+ ]),
7840+ new Document ([
7841+ '$id ' => 'unique1 ' ,
7842+ 'name ' => 'Unique ' ,
7843+ '$permissions ' => [
7844+ Permission::read (Role::any ()),
7845+ Permission::create (Role::any ()),
7846+ ],
7847+ ]),
7848+ ], onNext: function (Document $ doc ) use (&$ emittedIds ) {
7849+ $ emittedIds [] = $ doc ->getId ();
7850+ }, ignore: true );
7851+
7852+ $ this ->assertSame (2 , $ count );
7853+ $ this ->assertCount (2 , $ emittedIds );
7854+
7855+ // First occurrence wins
7856+ $ doc = $ database ->getDocument ($ col , 'dup ' );
7857+ $ this ->assertSame ('First ' , $ doc ->getAttribute ('name ' ));
7858+
7859+ // Second doc's extra permission should NOT exist (no ACL drift)
7860+ $ perms = $ doc ->getPermissions ();
7861+ foreach ($ perms as $ perm ) {
7862+ $ this ->assertStringNotContainsString ('extra ' , $ perm );
7863+ }
7864+
7865+ // unique1 should exist
7866+ $ unique = $ database ->getDocument ($ col , 'unique1 ' );
7867+ $ this ->assertSame ('Unique ' , $ unique ->getAttribute ('name ' ));
7868+
7869+ // Total: 2 documents
7870+ $ all = $ database ->find ($ col );
7871+ $ this ->assertCount (2 , $ all );
7872+ }
7873+
7874+ public function testCreateDocumentsIgnoreAllDuplicates (): void
7875+ {
7876+ /** @var Database $database */
7877+ $ database = $ this ->getDatabase ();
7878+
7879+ $ database ->createCollection (__FUNCTION__ );
7880+ $ database ->createAttribute (__FUNCTION__ , 'name ' , Database::VAR_STRING , 128 , true );
7881+
7882+ // Insert initial document
7883+ $ database ->createDocuments (__FUNCTION__ , [
7884+ new Document ([
7885+ '$id ' => 'existing ' ,
7886+ 'name ' => 'Original ' ,
7887+ '$permissions ' => [
7888+ Permission::read (Role::any ()),
7889+ Permission::create (Role::any ()),
7890+ ],
7891+ ]),
7892+ ]);
7893+
7894+ // With ignore, inserting only duplicates should succeed with no new rows
7895+ $ emittedIds = [];
7896+ $ count = $ database ->createDocuments (__FUNCTION__ , [
7897+ new Document ([
7898+ '$id ' => 'existing ' ,
7899+ 'name ' => 'Duplicate ' ,
7900+ '$permissions ' => [
7901+ Permission::read (Role::any ()),
7902+ Permission::create (Role::any ()),
7903+ ],
7904+ ]),
7905+ ], onNext: function (Document $ doc ) use (&$ emittedIds ) {
7906+ $ emittedIds [] = $ doc ->getId ();
7907+ }, ignore: true );
7908+
7909+ // All duplicates skipped, nothing inserted
7910+ $ this ->assertSame (0 , $ count );
7911+ $ this ->assertSame ([], $ emittedIds );
7912+
7913+ // Original document should be unchanged
7914+ $ doc = $ database ->getDocument (__FUNCTION__ , 'existing ' );
7915+ $ this ->assertSame ('Original ' , $ doc ->getAttribute ('name ' ));
7916+
7917+ // Still only 1 document
7918+ $ all = $ database ->find (__FUNCTION__ );
7919+ $ this ->assertCount (1 , $ all );
7920+ }
77257921}
0 commit comments