diff --git a/src/Database/Database.php b/src/Database/Database.php index cc1336625..a1573a7b0 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3283,7 +3283,7 @@ public function getDocument(string $collection, string $id, array $queries = [], $document = $this->decode($collection, $document, $selections); $this->map = []; - if ($this->resolveRelationships && (empty($selects) || !empty($nestedSelections))) { + if ($this->resolveRelationships && !empty($relationships) && (empty($selects) || !empty($nestedSelections))) { $document = $this->silent(fn () => $this->populateDocumentRelationships($collection, $document, $nestedSelections)); } @@ -3310,11 +3310,11 @@ public function getDocument(string $collection, string $id, array $queries = [], /** * @param Document $collection * @param Document $document - * @param array $queries + * @param array> $selects * @return Document * @throws DatabaseException */ - private function populateDocumentRelationships(Document $collection, Document $document, array $queries = []): Document + private function populateDocumentRelationships(Document $collection, Document $document, array $selects = []): Document { $attributes = $collection->getAttribute('attributes', []); @@ -3331,6 +3331,8 @@ private function populateDocumentRelationships(Document $collection, Document $d $twoWayKey = $relationship['options']['twoWayKey']; $side = $relationship['options']['side']; + $queries = $selects[$key] ?? []; + if (!empty($value)) { $k = $relatedCollection->getId() . ':' . $value . '=>' . $collection->getId() . ':' . $document->getId(); if ($relationType === Database::RELATION_ONE_TO_MANY) { @@ -6090,8 +6092,8 @@ public function find(string $collection, array $queries = [], string $forPermiss $results = $skipAuth ? Authorization::skip($getResults) : $getResults(); - foreach ($results as &$node) { - if ($this->resolveRelationships && (empty($selects) || !empty($nestedSelections))) { + foreach ($results as $index => $node) { + if ($this->resolveRelationships && !empty($relationships) && (empty($selects) || !empty($nestedSelections))) { $node = $this->silent(fn () => $this->populateDocumentRelationships($collection, $node, $nestedSelections)); } @@ -6101,6 +6103,8 @@ public function find(string $collection, array $queries = [], string $forPermiss if (!$node->isEmpty()) { $node->setAttribute('$collection', $collection->getId()); } + + $results[$index] = $node; } $this->trigger(self::EVENT_DOCUMENT_FIND, $results); @@ -6345,11 +6349,12 @@ public function encode(Document $collection, Document $document): Document $value = ($array) ? $value : [$value]; } - foreach ($value as &$node) { - if (($node !== null)) { + foreach ($value as $index => $node) { + if ($node !== null) { foreach ($filters as $filter) { $node = $this->encodeAttribute($filter, $node, $document); } + $value[$index] = $node; } } @@ -6783,7 +6788,7 @@ private function checkQueriesType(array $queries): void * * @param array $relationships * @param array $queries - * @return array + * @return array> $selects */ private function processRelationshipQueries( array $relationships, @@ -6802,7 +6807,8 @@ private function processRelationshipQueries( continue; } - $selectedKey = \explode('.', $value)[0]; + $nesting = \explode('.', $value); + $selectedKey = \array_shift($nesting); // Remove and return first item $relationship = \array_values(\array_filter( $relationships, @@ -6815,9 +6821,9 @@ private function processRelationshipQueries( // Shift the top level off the dot-path to pass the selection down the chain // 'foo.bar.baz' becomes 'bar.baz' - $nestedSelections[] = Query::select([ - \implode('.', \array_slice(\explode('.', $value), 1)) - ]); + + $nestingPath = \implode('.', $nesting); + $nestedSelections[$selectedKey][] = Query::select([$nestingPath]); $type = $relationship->getAttribute('options')['relationType']; $side = $relationship->getAttribute('options')['side']; @@ -6845,6 +6851,7 @@ private function processRelationshipQueries( break; } } + $query->setValues(\array_values($values)); } diff --git a/tests/e2e/Adapter/Scopes/RelationshipTests.php b/tests/e2e/Adapter/Scopes/RelationshipTests.php index 9db9d5be2..edc2f3e0b 100644 --- a/tests/e2e/Adapter/Scopes/RelationshipTests.php +++ b/tests/e2e/Adapter/Scopes/RelationshipTests.php @@ -27,6 +27,217 @@ trait RelationshipTests use ManyToOneTests; use ManyToManyTests; + public function testZoo(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + if (!$database->getAdapter()->getSupportForRelationships()) { + $this->expectNotToPerformAssertions(); + return; + } + + $database->createCollection('zoo'); + $database->createAttribute('zoo', 'name', Database::VAR_STRING, 256, true); + + $database->createCollection('veterinarians'); + $database->createAttribute('veterinarians', 'fullname', Database::VAR_STRING, 256, true); + + $database->createCollection('presidents'); + $database->createAttribute('presidents', 'first_name', Database::VAR_STRING, 256, true); + $database->createAttribute('presidents', 'last_name', Database::VAR_STRING, 256, true); + $database->createRelationship( + collection: 'presidents', + relatedCollection: 'veterinarians', + type: Database::RELATION_MANY_TO_MANY, + twoWay: true, + id: 'votes', + twoWayKey: 'presidents' + ); + + $database->createCollection('__animals'); + $database->createAttribute('__animals', 'name', Database::VAR_STRING, 256, true); + $database->createAttribute('__animals', 'age', Database::VAR_INTEGER, 0, false); + $database->createAttribute('__animals', 'price', Database::VAR_FLOAT, 0, false); + $database->createAttribute('__animals', 'date_of_birth', Database::VAR_DATETIME, 0, true, filters:['datetime']); + $database->createAttribute('__animals', 'longtext', Database::VAR_STRING, 100000000, false); + $database->createAttribute('__animals', 'is_active', Database::VAR_BOOLEAN, 0, false, default: true); + $database->createAttribute('__animals', 'integers', Database::VAR_INTEGER, 0, false, array: true); + $database->createAttribute('__animals', 'email', Database::VAR_STRING, 255, false); + $database->createAttribute('__animals', 'ip', Database::VAR_STRING, 255, false); + $database->createAttribute('__animals', 'url', Database::VAR_STRING, 255, false); + $database->createAttribute('__animals', 'enum', Database::VAR_STRING, 255, false); + + $database->createRelationship( + collection: 'presidents', + relatedCollection: '__animals', + type: Database::RELATION_ONE_TO_ONE, + twoWay: true, + id: 'animal', + twoWayKey: 'president' + ); + + $database->createRelationship( + collection: 'veterinarians', + relatedCollection: '__animals', + type: Database::RELATION_ONE_TO_MANY, + twoWay: true, + id: 'animals', + twoWayKey: 'veterinarian' + ); + + $database->createRelationship( + collection: '__animals', + relatedCollection: 'zoo', + type: Database::RELATION_MANY_TO_ONE, + twoWay: true, + id: 'zoo', + twoWayKey: 'animals' + ); + + $zoo1 = $database->createDocument('zoo', new Document([ + '$id' => 'zoo1', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'name' => 'Bronx Zoo' + ])); + + $animal1 = $database->createDocument('__animals', new Document([ + '$id' => 'iguana', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'name' => 'Iguana', + 'age' => 11, + 'price' => 50.5, + 'date_of_birth' => '1975-06-12', + 'longtext' => 'I am a pretty long text', + 'is_active' => true, + 'integers' => [1, 2, 3], + 'email' => 'iguana@appwrite.io', + 'enum' => 'maybe', + 'ip' => '127.0.0.1', + 'url' => 'https://appwrite.io/', + 'zoo' => $zoo1->getId(), + ])); + + $animal2 = $database->createDocument('__animals', new Document([ + '$id' => 'tiger', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'name' => 'Tiger', + 'age' => 5, + 'price' => 1000, + 'date_of_birth' => '2020-06-12', + 'longtext' => 'I am a hungry tiger', + 'is_active' => false, + 'integers' => [9, 2, 3], + 'email' => 'tiger@appwrite.io', + 'enum' => 'yes', + 'ip' => '255.0.0.1', + 'url' => 'https://appwrite.io/', + 'zoo' => $zoo1->getId(), + ])); + + $animal3 = $database->createDocument('__animals', new Document([ + '$id' => 'lama', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'name' => 'Lama', + 'age' => 15, + 'price' => 1000, + 'date_of_birth' => '1975-06-12', + 'is_active' => true, + 'integers' => null, + 'email' => null, + 'enum' => null, + 'ip' => '255.0.0.1', + 'url' => 'https://appwrite.io/', + 'zoo' => null, + ])); + + $veterinarian1 = $database->createDocument('veterinarians', new Document([ + '$id' => 'dr.pol', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'fullname' => 'The Incredible Dr. Pol', + 'animals' => ['iguana'], + ])); + + $veterinarian2 = $database->createDocument('veterinarians', new Document([ + '$id' => 'dr.seuss', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'fullname' => 'Dr. Seuss', + 'animals' => ['tiger'], + ])); + + $president1 = $database->createDocument('presidents', new Document([ + '$id' => 'trump', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'first_name' => 'Donald', + 'last_name' => 'Trump', + 'votes' => [ + $veterinarian1->getId(), + $veterinarian2->getId(), + ], + ])); + + $president2 = $database->createDocument('presidents', new Document([ + '$id' => 'bush', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'first_name' => 'George', + 'last_name' => 'Bush', + 'animal' => 'iguana', + ])); + + $president3 = $database->createDocument('presidents', new Document([ + '$id' => 'biden', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'first_name' => 'Joe', + 'last_name' => 'Biden', + 'animal' => 'tiger', + ])); + + var_dump('=== start === === start === === start === === start === === start === === start === === start === === start === === start ==='); + + $docs = $database->find( + 'veterinarians', + [ + Query::select([ + '*', + 'animals.*', + 'animals.zoo.*', + //'animals.president.*', + ]) + ] + ); + + var_dump($docs); + + //$this->assertEquals('shmuel', 'fogel'); + } + public function testDeleteRelatedCollection(): void { /** @var Database $database */