Skip to content

Commit

Permalink
Merge pull request #8 from event-engine/fix/ensure_unique_idx_for_exi…
Browse files Browse the repository at this point in the history
…sting_docs

Assert unique constraints for existing docs
  • Loading branch information
codeliner authored Nov 13, 2019
2 parents 47b7b07 + 68871eb commit c68ea51
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 5 deletions.
40 changes: 35 additions & 5 deletions src/InMemoryDocumentStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use EventEngine\DocumentStore\Exception\RuntimeException;
use EventEngine\DocumentStore\Exception\UnknownCollection;
use EventEngine\DocumentStore\Filter\AndFilter;
use EventEngine\DocumentStore\Filter\AnyFilter;
use EventEngine\DocumentStore\Filter\EqFilter;
use EventEngine\DocumentStore\Filter\Filter;
use EventEngine\DocumentStore\OrderBy\AndOrder;
Expand Down Expand Up @@ -89,6 +90,7 @@ public function hasCollectionIndex(string $collectionName, string $indexName): b
{
foreach ($this->inMemoryConnection['documentIndices'][$collectionName] as $index) {
if($index instanceof FieldIndex || $index instanceof MultiFieldIndex) {
echo $index->name();
if($index->name() === $indexName) {
return true;
}
Expand All @@ -105,11 +107,39 @@ public function hasCollectionIndex(string $collectionName, string $indexName): b
*/
public function addCollectionIndex(string $collectionName, Index $index): void
{
$this->assertHasCollection($collectionName);

if($index instanceof FieldIndex || $index instanceof MultiFieldIndex) {
$this->dropCollectionIndex($collectionName, $index->name());
}

$this->inMemoryConnection['documentIndices'][] = $index;
$docsCount = count($this->inMemoryConnection['documents'][$collectionName]);

if($docsCount > 1 && ($index instanceof FieldIndex || $index instanceof MultiFieldIndex) && $index->unique()) {
$uniqueErrMsg = "Unique constraint violation. Cannot add unique index because existing documents conflict with it!";
$assertMethod = $index instanceof FieldIndex? 'assertUniqueFieldConstraint' : 'assertMultiFieldUniqueConstraint';
$checkCount = 0;
$halfOfDocs = $docsCount / 2;

foreach ($this->inMemoryConnection['documents'][$collectionName] as $docId => $document) {
if($checkCount > $halfOfDocs) {
break;
}

// Temp unset to prevent false positives
unset($this->inMemoryConnection['documents'][$collectionName][$docId]);
try {
$this->{$assertMethod}($collectionName, (string)$docId, $document, $index, $uniqueErrMsg);
} catch (\Throwable $e) {
$this->inMemoryConnection['documents'][$collectionName][$docId] = $document;
throw $e;
}

$checkCount++;
}
}

$this->inMemoryConnection['documentIndices'][$collectionName][] = $index;
}

/**
Expand Down Expand Up @@ -326,7 +356,7 @@ private function assertUniqueConstraints(string $collectionName, string $docId,
}
}

private function assertUniqueFieldConstraint(string $collectionName, string $docId, array $docOrSubset, FieldIndex $index): void
private function assertUniqueFieldConstraint(string $collectionName, string $docId, array $docOrSubset, FieldIndex $index, string $errMsg = null): void
{
if(!$index->unique()) {
return;
Expand All @@ -346,14 +376,14 @@ private function assertUniqueFieldConstraint(string $collectionName, string $doc

foreach ($existingDocs as $existingDoc) {
throw new RuntimeException(
"Unique constraint violation. Cannot insert or update document with id $docId, because a document with same value for field: {$index->field()} exists already!"
$errMsg ?? "Unique constraint violation. Cannot insert or update document with id $docId, because a document with same value for field: {$index->field()} exists already!"
);
}

return;
}

private function assertMultiFieldUniqueConstraint(string $collectionName, string $docId, array $docOrSubset, MultiFieldIndex $index): void
private function assertMultiFieldUniqueConstraint(string $collectionName, string $docId, array $docOrSubset, MultiFieldIndex $index, string $errMsg = null): void
{
if(!$index->unique()) {
return;
Expand Down Expand Up @@ -402,7 +432,7 @@ private function assertMultiFieldUniqueConstraint(string $collectionName, string
foreach ($existingDocs as $existingDoc) {
$fieldNamesStr = implode(", ", $fieldNames);
throw new RuntimeException(
"Unique constraint violation. Cannot insert or update document with id $docId, because a document with same values for fields: {$fieldNamesStr} exists already!"
$errMsg ?? "Unique constraint violation. Cannot insert or update document with id $docId, because a document with same values for fields: {$fieldNamesStr} exists already!"
);
}

Expand Down
64 changes: 64 additions & 0 deletions tests/InMemoryDocumentStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,68 @@ public function it_ensures_unique_constraints_for_multiple_fields()
$this->expectExceptionMessageRegExp('/^Unique constraint violation/');
$this->store->updateDoc('test', '2', ['some' => ['prop' => 'foo']]);
}

/**
* @test
*/
public function it_blocks_adding_a_unique_index_if_it_conflicts_with_existing_docs()
{
$this->store->addCollection('test');

$this->store->addDoc('test', '1', ['some' => ['prop' => 'foo', 'other' => ['prop' => 'bat']]]);
$this->store->addDoc('test', '2', ['some' => ['prop' => 'bar', 'other' => ['prop' => 'bat']]]);
$this->store->addDoc('test', '3', ['some' => ['prop' => 'bar']]);

$this->expectExceptionMessageRegExp('/^Unique constraint violation/');
$uniqueIndex = FieldIndex::forField('some.prop', FieldIndex::SORT_ASC, true);
$this->store->addCollectionIndex('test', $uniqueIndex);
}

/**
* @test
*/
public function it_does_not_block_adding_a_unique_index_if_no_conflict_exists()
{
$this->store->addCollection('test');

$this->store->addDoc('test', '1', ['some' => ['prop' => 'foo', 'other' => ['prop' => 'bat']]]);
$this->store->addDoc('test', '2', ['some' => ['prop' => 'bar', 'other' => ['prop' => 'baz']]]);
$this->store->addDoc('test', '3', ['some' => ['prop' => 'bar']]);

$uniqueIndex = FieldIndex::namedIndexForField('test_idx', 'some.other.prop', FieldIndex::SORT_ASC, true);
$this->store->addCollectionIndex('test', $uniqueIndex);
$this->assertTrue($this->store->hasCollectionIndex('test', 'test_idx'));
}

/**
* @test
*/
public function it_blocks_adding_a_unique_multi_field_index_if_it_conflicts_with_existing_docs()
{
$this->store->addCollection('test');

$this->store->addDoc('test', '1', ['some' => ['prop' => 'foo', 'other' => ['prop' => 'bat']]]);
$this->store->addDoc('test', '2', ['some' => ['prop' => 'foo', 'other' => ['prop' => 'bat']]]);
$this->store->addDoc('test', '3', ['some' => ['prop' => 'bar']]);

$this->expectExceptionMessageRegExp('/^Unique constraint violation/');
$uniqueIndex = MultiFieldIndex::forFields(['some.prop', 'some.other.prop'], true);
$this->store->addCollectionIndex('test', $uniqueIndex);
}

/**
* @test
*/
public function it_does_not_block_adding_a_unique_multi_field_index_if_no_conflict_exists()
{
$this->store->addCollection('test');

$this->store->addDoc('test', '1', ['some' => ['prop' => 'foo', 'other' => ['prop' => 'bat']]]);
$this->store->addDoc('test', '2', ['some' => ['prop' => 'bar', 'other' => ['prop' => 'bat']]]);
$this->store->addDoc('test', '3', ['some' => ['prop' => 'bar']]);

$uniqueIndex = MultiFieldIndex::namedIndexForFields('test_idx', ['some.prop', 'some.other.prop'], true);
$this->store->addCollectionIndex('test', $uniqueIndex);
$this->assertTrue($this->store->hasCollectionIndex('test', 'test_idx'));
}
}

0 comments on commit c68ea51

Please sign in to comment.