From 0ce0f45bb9cc7b785c14f8a10adccf04e2efb937 Mon Sep 17 00:00:00 2001 From: Martin Schilling Date: Tue, 17 Mar 2020 15:17:26 +0100 Subject: [PATCH 1/2] Added functionality to retrieve ids of all docs that match a given filter --- src/DocumentStore.php | 7 +++++++ src/InMemoryDocumentStore.php | 21 +++++++++++++++++++++ tests/InMemoryDocumentStoreTest.php | 23 +++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/DocumentStore.php b/src/DocumentStore.php index f12d721..20a6e56 100644 --- a/src/DocumentStore.php +++ b/src/DocumentStore.php @@ -136,4 +136,11 @@ public function getDoc(string $collectionName, string $docId): ?array; * @throws UnknownCollection */ public function filterDocs(string $collectionName, Filter $filter, int $skip = null, int $limit = null, OrderBy $orderBy = null): \Traversable; + + /** + * @param string $collectionName + * @param Filter $filter + * @return array + */ + public function filterDocIds(string $collectionName, Filter $filter): array; } diff --git a/src/InMemoryDocumentStore.php b/src/InMemoryDocumentStore.php index 361eb07..9a01685 100644 --- a/src/InMemoryDocumentStore.php +++ b/src/InMemoryDocumentStore.php @@ -323,6 +323,27 @@ public function filterDocs( return new \ArrayIterator($filteredDocs); } + /** + * @param string $collectionName + * @param Filter $filter + * @return array + */ + public function filterDocIds( + string $collectionName, + Filter $filter + ): array { + $this->assertHasCollection($collectionName); + + $docIds = []; + foreach ($this->inMemoryConnection['documents'][$collectionName] as $docId => $doc) { + if ($filter->match($doc, (string)$docId)) { + $docIds[] = $docId; + } + } + + return $docIds; + } + private function hasDoc(string $collectionName, string $docId): bool { if (! $this->hasCollection($collectionName)) { diff --git a/tests/InMemoryDocumentStoreTest.php b/tests/InMemoryDocumentStoreTest.php index 641e112..292735f 100644 --- a/tests/InMemoryDocumentStoreTest.php +++ b/tests/InMemoryDocumentStoreTest.php @@ -4,8 +4,12 @@ namespace EventEngineTest\DocumentStore; use EventEngine\DocumentStore\FieldIndex; +use EventEngine\DocumentStore\Filter\AndFilter; use EventEngine\DocumentStore\Filter\AnyFilter; use EventEngine\DocumentStore\Filter\EqFilter; +use EventEngine\DocumentStore\Filter\GtFilter; +use EventEngine\DocumentStore\Filter\LtFilter; +use EventEngine\DocumentStore\Filter\OrFilter; use EventEngine\DocumentStore\InMemoryDocumentStore; use EventEngine\DocumentStore\MultiFieldIndex; use EventEngine\Persistence\InMemoryConnection; @@ -105,6 +109,25 @@ public function it_updates_a_subset_of_a_doc() $this->assertEquals(42, $filteredDocs[0]['some']['other']['nested']); } + /** + * @test + */ + public function it_retrieves_doc_ids_by_filter() + { + $this->store->addCollection('test'); + + $this->store->addDoc('test', 'a', ['number' => 10]); + $this->store->addDoc('test', 'b', ['number' => 20]); + $this->store->addDoc('test', 'c', ['number' => 30]); + + $result = $this->store->filterDocIds('test', new OrFilter( + new GtFilter('number', 21), + new LtFilter('number', 19) + )); + + $this->assertEquals(['a', 'c'], $result); + } + /** * @test */ From e02c1140fd5e852d3dd5e7b3abacea6ae3efd0b0 Mon Sep 17 00:00:00 2001 From: Martin Schilling Date: Wed, 18 Mar 2020 10:54:21 +0100 Subject: [PATCH 2/2] Attempted to fix bug introduced by array_replace_recursive and its behavior regarding sequential arrays --- src/InMemoryDocumentStore.php | 39 +++++++++++++++++++++-- tests/InMemoryDocumentStoreTest.php | 48 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/InMemoryDocumentStore.php b/src/InMemoryDocumentStore.php index 9a01685..f2174b9 100644 --- a/src/InMemoryDocumentStore.php +++ b/src/InMemoryDocumentStore.php @@ -201,7 +201,7 @@ public function updateDoc(string $collectionName, string $docId, array $docOrSub $this->assertDocExists($collectionName, $docId); $this->assertUniqueConstraints($collectionName, $docId, $docOrSubset); - $this->inMemoryConnection['documents'][$collectionName][$docId] = \array_replace_recursive( + $this->inMemoryConnection['documents'][$collectionName][$docId] = $this->arrayReplaceRecursiveAssocOnly( $this->inMemoryConnection['documents'][$collectionName][$docId], $docOrSubset ); @@ -425,7 +425,7 @@ private function assertMultiFieldUniqueConstraint(string $collectionName, string if($this->hasDoc($collectionName, $docId)) { $effectedDoc = $this->getDoc($collectionName, $docId); - $docOrSubset = \array_replace_recursive($effectedDoc, $docOrSubset); + $docOrSubset = $this->arrayReplaceRecursiveAssocOnly($effectedDoc, $docOrSubset); } $reader = new ArrayReader($docOrSubset); @@ -537,4 +537,39 @@ private function sort(&$docs, OrderBy $orderBy) return $docCmp($docA, $docB, $orderBy); }); } + + /** + * The internal array_replace_recursive function also replaces sequential arrays recursively. This method aims to + * behave identical to array_replace_recursive but only when dealing with associative arrays. Sequential arrays + * are handled as if they were scalar types instead. + * + * @param array $array1 + * @param array $array2 + * @return array + */ + private function arrayReplaceRecursiveAssocOnly(array $array1, array $array2): array + { + foreach ($array2 as $key2 => $value2) { + $bothValuesArraysAndAtLeastOneAssoc = \is_array($value2) && + isset($array1[$key2]) && \is_array($array1[$key2]) && + !($this->isSequentialArray($value2) && $this->isSequentialArray($array1[$key2])); + + if ($bothValuesArraysAndAtLeastOneAssoc) { + $array1[$key2] = $this->arrayReplaceRecursiveAssocOnly($array1[$key2], $value2); + } else { + $array1[$key2] = $value2; + } + } + + return $array1; + } + + private function isSequentialArray(array $array): bool + { + if (\count($array) === 0) { + return true; + } + + return \array_keys($array) === \range(0, \count($array) - 1); + } } diff --git a/tests/InMemoryDocumentStoreTest.php b/tests/InMemoryDocumentStoreTest.php index 292735f..bf7cd7f 100644 --- a/tests/InMemoryDocumentStoreTest.php +++ b/tests/InMemoryDocumentStoreTest.php @@ -326,4 +326,52 @@ public function it_deletes_many() $this->assertCount(1, $filteredDocs); $this->assertEquals(['some' => ['prop' => 'bar']], $filteredDocs[0]); } + + /** + * @test + */ + public function it_does_not_update_numeric_arrays_recursively() + { + $this->store->addCollection('test'); + + $this->store->addDoc('test', 'doc', [ + 'a' => ['a' => 10, 'b' => 20], + 'b' => [10, 20, 30], + 'c' => [], + 'd' => [false, true], + 'e' => ['a' => 'b'], + 'f' => [10, 20], + 'g' => ['x' => 10, 'y' => 20], + 'h' => [11], + 'j' => 'foo' + ]); + + $this->store->updateDoc('test', 'doc', [ + 'a' => ['b' => 21, 'c' => 30], + 'b' => [10, 30], + 'c' => [true], + 'd' => [], + 'e' => [], + 'f' => ['x' => 10, 'y' => 20], + 'g' => [30, 40], + 'i' => [22], + 'j' => ['bar'] + ]); + + $this->assertEquals( + [ + 'a' => ['a' => 10, 'b' => 21, 'c' => 30], + 'b' => [10, 30], + 'c' => [true], + 'd' => [], + 'e' => ['a' => 'b'], + 'f' => [10, 20, 'x' => 10, 'y' => 20], + 'g' => ['x' => 10, 'y' => 20, 30, 40], + 'h' => [11], + 'i' => [22], + 'j' => ['bar'] + ], + $this->store->getDoc('test', 'doc') + ); + } }