1414use Codeliner \ArrayReader \ArrayReader ;
1515use EventEngine \DocumentStore \Exception \RuntimeException ;
1616use EventEngine \DocumentStore \Exception \UnknownCollection ;
17+ use EventEngine \DocumentStore \Filter \AndFilter ;
18+ use EventEngine \DocumentStore \Filter \EqFilter ;
1719use EventEngine \DocumentStore \Filter \Filter ;
1820use EventEngine \DocumentStore \OrderBy \AndOrder ;
1921use EventEngine \DocumentStore \OrderBy \Asc ;
@@ -68,6 +70,7 @@ public function hasCollection(string $collectionName): bool
6870 public function addCollection (string $ collectionName , Index ...$ indices ): void
6971 {
7072 $ this ->inMemoryConnection ['documents ' ][$ collectionName ] = [];
73+ $ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ] = $ indices ;
7174 }
7275
7376 /**
@@ -78,12 +81,20 @@ public function dropCollection(string $collectionName): void
7881 {
7982 if ($ this ->hasCollection ($ collectionName )) {
8083 unset($ this ->inMemoryConnection ['documents ' ][$ collectionName ]);
84+ unset($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ]);
8185 }
8286 }
8387
8488 public function hasCollectionIndex (string $ collectionName , string $ indexName ): bool
8589 {
86- //InMemoryDocumentStore ignores indices
90+ foreach ($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ] as $ index ) {
91+ if ($ index instanceof FieldIndex || $ index instanceof MultiFieldIndex) {
92+ if ($ index ->name () === $ indexName ) {
93+ return true ;
94+ }
95+ }
96+ }
97+
8798 return false ;
8899 }
89100
@@ -94,7 +105,11 @@ public function hasCollectionIndex(string $collectionName, string $indexName): b
94105 */
95106 public function addCollectionIndex (string $ collectionName , Index $ index ): void
96107 {
97- //InMemoryDocumentStore ignores indices
108+ if ($ index instanceof FieldIndex || $ index instanceof MultiFieldIndex) {
109+ $ this ->dropCollectionIndex ($ collectionName , $ index ->name ());
110+ }
111+
112+ $ this ->inMemoryConnection ['documentIndices ' ][] = $ index ;
98113 }
99114
100115 /**
@@ -104,7 +119,27 @@ public function addCollectionIndex(string $collectionName, Index $index): void
104119 */
105120 public function dropCollectionIndex (string $ collectionName , $ index ): void
106121 {
107- //InMemoryDocumentStore ignores indices
122+ if (is_string ($ index )) {
123+ foreach ($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ] as $ idxI => $ existingIndex ) {
124+ if ($ existingIndex instanceof FieldIndex || $ existingIndex instanceof MultiFieldIndex) {
125+ if ($ existingIndex ->name () === $ index ) {
126+ unset($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ][$ idxI ]);
127+ }
128+ }
129+ }
130+
131+ $ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ] = array_values ($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ]);
132+
133+ return ;
134+ }
135+
136+ foreach ($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ] as $ idxI => $ existingIndex ) {
137+ if ($ existingIndex === $ index ) {
138+ unset($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ][$ idxI ]);
139+ }
140+ }
141+
142+ $ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ] = array_values ($ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ]);
108143 }
109144
110145 /**
@@ -121,6 +156,8 @@ public function addDoc(string $collectionName, string $docId, array $doc): void
121156 throw new RuntimeException ("Cannot add doc with id $ docId. The doc already exists in collection $ collectionName " );
122157 }
123158
159+ $ this ->assertUniqueConstraints ($ collectionName , $ docId , $ doc );
160+
124161 $ this ->inMemoryConnection ['documents ' ][$ collectionName ][$ docId ] = $ doc ;
125162 }
126163
@@ -133,8 +170,9 @@ public function addDoc(string $collectionName, string $docId, array $doc): void
133170 public function updateDoc (string $ collectionName , string $ docId , array $ docOrSubset ): void
134171 {
135172 $ this ->assertDocExists ($ collectionName , $ docId );
173+ $ this ->assertUniqueConstraints ($ collectionName , $ docId , $ docOrSubset );
136174
137- $ this ->inMemoryConnection ['documents ' ][$ collectionName ][$ docId ] = \array_merge (
175+ $ this ->inMemoryConnection ['documents ' ][$ collectionName ][$ docId ] = \array_replace_recursive (
138176 $ this ->inMemoryConnection ['documents ' ][$ collectionName ][$ docId ],
139177 $ docOrSubset
140178 );
@@ -228,11 +266,13 @@ public function filterDocs(
228266 $ filteredDocs = [];
229267
230268 foreach ($ this ->inMemoryConnection ['documents ' ][$ collectionName ] as $ docId => $ doc ) {
231- if ($ filter ->match ($ doc , $ docId )) {
269+ if ($ filter ->match ($ doc , ( string ) $ docId )) {
232270 $ filteredDocs [$ docId ] = $ doc ;
233271 }
234272 }
235273
274+ $ filteredDocs = \array_values ($ filteredDocs );
275+
236276 if ($ orderBy !== null ) {
237277 $ this ->sort ($ filteredDocs , $ orderBy );
238278 }
@@ -271,6 +311,104 @@ private function assertDocExists(string $collectionName, string $docId): void
271311 }
272312 }
273313
314+ private function assertUniqueConstraints (string $ collectionName , string $ docId , array $ docOrSubset ): void
315+ {
316+ $ indices = $ this ->inMemoryConnection ['documentIndices ' ][$ collectionName ];
317+
318+ foreach ($ indices as $ index ) {
319+ if ($ index instanceof FieldIndex) {
320+ $ this ->assertUniqueFieldConstraint ($ collectionName , $ docId , $ docOrSubset , $ index );
321+ }
322+
323+ if ($ index instanceof MultiFieldIndex) {
324+ $ this ->assertMultiFieldUniqueConstraint ($ collectionName , $ docId , $ docOrSubset , $ index );
325+ }
326+ }
327+ }
328+
329+ private function assertUniqueFieldConstraint (string $ collectionName , string $ docId , array $ docOrSubset , FieldIndex $ index ): void
330+ {
331+ if (!$ index ->unique ()) {
332+ return ;
333+ }
334+
335+ $ reader = new ArrayReader ($ docOrSubset );
336+
337+ if (!$ reader ->pathExists ($ index ->field ())) {
338+ return ;
339+ }
340+
341+ $ value = $ reader ->mixedValue ($ index ->field ());
342+
343+ $ check = new EqFilter ($ index ->field (), $ value );
344+
345+ $ existingDocs = $ this ->filterDocs ($ collectionName , $ check );
346+
347+ foreach ($ existingDocs as $ existingDoc ) {
348+ throw new RuntimeException (
349+ "Unique constraint violation. Cannot insert or update document with id $ docId, because a document with same value for field: {$ index ->field ()} exists already! "
350+ );
351+ }
352+
353+ return ;
354+ }
355+
356+ private function assertMultiFieldUniqueConstraint (string $ collectionName , string $ docId , array $ docOrSubset , MultiFieldIndex $ index ): void
357+ {
358+ if (!$ index ->unique ()) {
359+ return ;
360+ }
361+
362+ if ($ this ->hasDoc ($ collectionName , $ docId )) {
363+ $ effectedDoc = $ this ->getDoc ($ collectionName , $ docId );
364+ $ docOrSubset = \array_replace_recursive ($ effectedDoc , $ docOrSubset );
365+ }
366+
367+ $ reader = new ArrayReader ($ docOrSubset );
368+
369+ $ checkList = [];
370+ $ notExistingFieldsCheckList = [];
371+ $ fieldNames = [];
372+
373+ foreach ($ index ->fields () as $ fieldIndex ) {
374+ $ fieldNames [] = $ fieldIndex ->field ();
375+ if ($ reader ->pathExists ($ fieldIndex ->field ())) {
376+ $ checkList [] = new EqFilter ($ fieldIndex ->field (), $ reader ->mixedValue ($ fieldIndex ->field ()));
377+ } else {
378+ $ notExistingFieldsCheckList [] = new EqFilter ($ fieldIndex ->field (), null );
379+ }
380+ }
381+
382+ if (count ($ checkList ) === 0 ) {
383+ return ;
384+ }
385+
386+ $ checkList = array_merge ($ checkList , $ notExistingFieldsCheckList );
387+
388+ if (count ($ checkList ) > 1 ) {
389+ $ a = $ checkList [0 ];
390+ $ b = $ checkList [1 ];
391+ $ rest = array_slice ($ checkList , 2 );
392+ if (!$ rest ) {
393+ $ rest = [];
394+ }
395+ $ checkList = new AndFilter ($ a , $ b , ...$ rest );
396+ } else {
397+ $ checkList = $ checkList [0 ];
398+ }
399+
400+ $ existingDocs = $ this ->filterDocs ($ collectionName , $ checkList );
401+
402+ foreach ($ existingDocs as $ existingDoc ) {
403+ $ fieldNamesStr = implode (", " , $ fieldNames );
404+ throw new RuntimeException (
405+ "Unique constraint violation. Cannot insert or update document with id $ docId, because a document with same values for fields: {$ fieldNamesStr } exists already! "
406+ );
407+ }
408+
409+ return ;
410+ }
411+
274412 private function sort (&$ docs , OrderBy $ orderBy )
275413 {
276414 $ defaultCmp = function ($ a , $ b ) {
0 commit comments