Skip to content

Commit 63d9902

Browse files
committed
Replace ignore param with skipDuplicates scope guard
- Add skipDuplicates(callable) scope guard on Database, following existing pattern (skipRelationships, skipValidation, etc.) - Remove bool $ignore parameter from createDocuments signature - Mirror propagates skipDuplicates state to source and destination - Update tests to use $database->skipDuplicates(function() { ... })
1 parent 2906dda commit 63d9902

3 files changed

Lines changed: 96 additions & 70 deletions

File tree

src/Database/Database.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ class Database
417417

418418
protected bool $preserveDates = false;
419419

420+
protected bool $skipDuplicates = false;
421+
420422
protected bool $preserveSequence = false;
421423

422424
protected int $maxQueryValues = 5000;
@@ -842,6 +844,18 @@ public function skipRelationshipsExistCheck(callable $callback): mixed
842844
}
843845
}
844846

847+
public function skipDuplicates(callable $callback): mixed
848+
{
849+
$previous = $this->skipDuplicates;
850+
$this->skipDuplicates = true;
851+
852+
try {
853+
return $callback();
854+
} finally {
855+
$this->skipDuplicates = $previous;
856+
}
857+
}
858+
845859
/**
846860
* Trigger callback for events
847861
*
@@ -5621,7 +5635,6 @@ public function createDocument(string $collection, Document $document): Document
56215635
* @param int $batchSize
56225636
* @param (callable(Document): void)|null $onNext
56235637
* @param (callable(Throwable): void)|null $onError
5624-
* @param bool $ignore If true, silently ignore duplicate documents instead of throwing
56255638
* @return int
56265639
* @throws AuthorizationException
56275640
* @throws StructureException
@@ -5634,7 +5647,6 @@ public function createDocuments(
56345647
int $batchSize = self::INSERT_BATCH_SIZE,
56355648
?callable $onNext = null,
56365649
?callable $onError = null,
5637-
bool $ignore = false,
56385650
): int {
56395651
if (!$this->adapter->getSharedTables() && $this->adapter->getTenantPerDocument()) {
56405652
throw new DatabaseException('Shared tables must be enabled if tenant per document is enabled.');
@@ -5656,6 +5668,7 @@ public function createDocuments(
56565668
$modified = 0;
56575669

56585670
$tenantPerDocument = $this->adapter->getSharedTables() && $this->adapter->getTenantPerDocument();
5671+
$ignore = $this->skipDuplicates;
56595672

56605673
// Deduplicate intra-batch documents by ID (tenant-aware). First occurrence wins.
56615674
if ($ignore) {

src/Database/Mirror.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -600,17 +600,19 @@ public function createDocuments(
600600
int $batchSize = self::INSERT_BATCH_SIZE,
601601
?callable $onNext = null,
602602
?callable $onError = null,
603-
bool $ignore = false,
604603
): int {
605-
$modified = $this->source->createDocuments(
604+
$createFn = fn () => $this->source->createDocuments(
606605
$collection,
607606
$documents,
608607
$batchSize,
609608
$onNext,
610609
$onError,
611-
$ignore,
612610
);
613611

612+
$modified = $this->skipDuplicates
613+
? $this->source->skipDuplicates($createFn)
614+
: $createFn();
615+
614616
if (
615617
\in_array($collection, self::SOURCE_ONLY_COLLECTIONS)
616618
|| $this->destination === null
@@ -641,16 +643,19 @@ public function createDocuments(
641643
$clones[] = $clone;
642644
}
643645

644-
$this->destination->withPreserveDates(
646+
$destFn = fn () => $this->destination->withPreserveDates(
645647
fn () =>
646648
$this->destination->createDocuments(
647649
$collection,
648650
$clones,
649651
$batchSize,
650-
ignore: $ignore,
651652
)
652653
);
653654

655+
$this->skipDuplicates
656+
? $this->destination->skipDuplicates($destFn)
657+
: $destFn();
658+
654659
foreach ($clones as $clone) {
655660
foreach ($this->writeFilters as $filter) {
656661
$filter->afterCreateDocument(

tests/e2e/Adapter/Scopes/DocumentTests.php

Lines changed: 71 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7768,28 +7768,31 @@ public function testCreateDocumentsIgnoreDuplicates(): void
77687768
$this->assertNotEmpty($e->getMessage());
77697769
}
77707770

7771-
// With ignore, duplicates should be silently skipped
7771+
// With skipDuplicates, duplicates should be silently skipped
77727772
$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);
7773+
$collection = __FUNCTION__;
7774+
$count = $database->skipDuplicates(function () use ($database, $collection, &$emittedIds) {
7775+
return $database->createDocuments($collection, [
7776+
new Document([
7777+
'$id' => 'doc1',
7778+
'name' => 'Duplicate A',
7779+
'$permissions' => [
7780+
Permission::read(Role::any()),
7781+
Permission::create(Role::any()),
7782+
],
7783+
]),
7784+
new Document([
7785+
'$id' => 'doc3',
7786+
'name' => 'New C',
7787+
'$permissions' => [
7788+
Permission::read(Role::any()),
7789+
Permission::create(Role::any()),
7790+
],
7791+
]),
7792+
], onNext: function (Document $doc) use (&$emittedIds) {
7793+
$emittedIds[] = $doc->getId();
7794+
});
7795+
});
77937796

77947797
// Only doc3 was new, doc1 was skipped as duplicate
77957798
$this->assertSame(1, $count);
@@ -7819,35 +7822,37 @@ public function testCreateDocumentsIgnoreIntraBatchDuplicates(): void
78197822

78207823
// Two docs with same ID in one batch — first wins, second is deduplicated
78217824
$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);
7825+
$count = $database->skipDuplicates(function () use ($database, $col, &$emittedIds) {
7826+
return $database->createDocuments($col, [
7827+
new Document([
7828+
'$id' => 'dup',
7829+
'name' => 'First',
7830+
'$permissions' => [
7831+
Permission::read(Role::any()),
7832+
Permission::create(Role::any()),
7833+
],
7834+
]),
7835+
new Document([
7836+
'$id' => 'dup',
7837+
'name' => 'Second',
7838+
'$permissions' => [
7839+
Permission::read(Role::any()),
7840+
Permission::create(Role::any()),
7841+
Permission::update(Role::user('extra')),
7842+
],
7843+
]),
7844+
new Document([
7845+
'$id' => 'unique1',
7846+
'name' => 'Unique',
7847+
'$permissions' => [
7848+
Permission::read(Role::any()),
7849+
Permission::create(Role::any()),
7850+
],
7851+
]),
7852+
], onNext: function (Document $doc) use (&$emittedIds) {
7853+
$emittedIds[] = $doc->getId();
7854+
});
7855+
});
78517856

78527857
$this->assertSame(2, $count);
78537858
$this->assertCount(2, $emittedIds);
@@ -7891,20 +7896,23 @@ public function testCreateDocumentsIgnoreAllDuplicates(): void
78917896
]),
78927897
]);
78937898

7894-
// With ignore, inserting only duplicates should succeed with no new rows
7899+
// With skipDuplicates, inserting only duplicates should succeed with no new rows
78957900
$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);
7901+
$collection = __FUNCTION__;
7902+
$count = $database->skipDuplicates(function () use ($database, $collection, &$emittedIds) {
7903+
return $database->createDocuments($collection, [
7904+
new Document([
7905+
'$id' => 'existing',
7906+
'name' => 'Duplicate',
7907+
'$permissions' => [
7908+
Permission::read(Role::any()),
7909+
Permission::create(Role::any()),
7910+
],
7911+
]),
7912+
], onNext: function (Document $doc) use (&$emittedIds) {
7913+
$emittedIds[] = $doc->getId();
7914+
});
7915+
});
79087916

79097917
// All duplicates skipped, nothing inserted
79107918
$this->assertSame(0, $count);

0 commit comments

Comments
 (0)