Skip to content

Commit 0e9b85b

Browse files
authored
Merge pull request #643 from utopia-php/deep-select-relationship
Select nested relationships
2 parents cd961f1 + 153fe2c commit 0e9b85b

File tree

2 files changed

+230
-12
lines changed

2 files changed

+230
-12
lines changed

src/Database/Database.php

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3283,7 +3283,7 @@ public function getDocument(string $collection, string $id, array $queries = [],
32833283
$document = $this->decode($collection, $document, $selections);
32843284
$this->map = [];
32853285

3286-
if ($this->resolveRelationships && (empty($selects) || !empty($nestedSelections))) {
3286+
if ($this->resolveRelationships && !empty($relationships) && (empty($selects) || !empty($nestedSelections))) {
32873287
$document = $this->silent(fn () => $this->populateDocumentRelationships($collection, $document, $nestedSelections));
32883288
}
32893289

@@ -3310,11 +3310,11 @@ public function getDocument(string $collection, string $id, array $queries = [],
33103310
/**
33113311
* @param Document $collection
33123312
* @param Document $document
3313-
* @param array<Query> $queries
3313+
* @param array<string, array<Query>> $selects
33143314
* @return Document
33153315
* @throws DatabaseException
33163316
*/
3317-
private function populateDocumentRelationships(Document $collection, Document $document, array $queries = []): Document
3317+
private function populateDocumentRelationships(Document $collection, Document $document, array $selects = []): Document
33183318
{
33193319
$attributes = $collection->getAttribute('attributes', []);
33203320

@@ -3331,6 +3331,8 @@ private function populateDocumentRelationships(Document $collection, Document $d
33313331
$twoWayKey = $relationship['options']['twoWayKey'];
33323332
$side = $relationship['options']['side'];
33333333

3334+
$queries = $selects[$key] ?? [];
3335+
33343336
if (!empty($value)) {
33353337
$k = $relatedCollection->getId() . ':' . $value . '=>' . $collection->getId() . ':' . $document->getId();
33363338
if ($relationType === Database::RELATION_ONE_TO_MANY) {
@@ -6091,8 +6093,8 @@ public function find(string $collection, array $queries = [], string $forPermiss
60916093

60926094
$results = $skipAuth ? Authorization::skip($getResults) : $getResults();
60936095

6094-
foreach ($results as &$node) {
6095-
if ($this->resolveRelationships && (empty($selects) || !empty($nestedSelections))) {
6096+
foreach ($results as $index => $node) {
6097+
if ($this->resolveRelationships && !empty($relationships) && (empty($selects) || !empty($nestedSelections))) {
60966098
$node = $this->silent(fn () => $this->populateDocumentRelationships($collection, $node, $nestedSelections));
60976099
}
60986100

@@ -6102,6 +6104,8 @@ public function find(string $collection, array $queries = [], string $forPermiss
61026104
if (!$node->isEmpty()) {
61036105
$node->setAttribute('$collection', $collection->getId());
61046106
}
6107+
6108+
$results[$index] = $node;
61056109
}
61066110

61076111
$this->trigger(self::EVENT_DOCUMENT_FIND, $results);
@@ -6351,11 +6355,12 @@ public function encode(Document $collection, Document $document): Document
63516355
$value = ($array) ? $value : [$value];
63526356
}
63536357

6354-
foreach ($value as &$node) {
6355-
if (($node !== null)) {
6358+
foreach ($value as $index => $node) {
6359+
if ($node !== null) {
63566360
foreach ($filters as $filter) {
63576361
$node = $this->encodeAttribute($filter, $node, $document);
63586362
}
6363+
$value[$index] = $node;
63596364
}
63606365
}
63616366

@@ -6788,7 +6793,7 @@ private function checkQueriesType(array $queries): void
67886793
*
67896794
* @param array<Document> $relationships
67906795
* @param array<Query> $queries
6791-
* @return array<Query>
6796+
* @return array<string, array<Query>> $selects
67926797
*/
67936798
private function processRelationshipQueries(
67946799
array $relationships,
@@ -6807,7 +6812,8 @@ private function processRelationshipQueries(
68076812
continue;
68086813
}
68096814

6810-
$selectedKey = \explode('.', $value)[0];
6815+
$nesting = \explode('.', $value);
6816+
$selectedKey = \array_shift($nesting); // Remove and return first item
68116817

68126818
$relationship = \array_values(\array_filter(
68136819
$relationships,
@@ -6820,9 +6826,9 @@ private function processRelationshipQueries(
68206826

68216827
// Shift the top level off the dot-path to pass the selection down the chain
68226828
// 'foo.bar.baz' becomes 'bar.baz'
6823-
$nestedSelections[] = Query::select([
6824-
\implode('.', \array_slice(\explode('.', $value), 1))
6825-
]);
6829+
6830+
$nestingPath = \implode('.', $nesting);
6831+
$nestedSelections[$selectedKey][] = Query::select([$nestingPath]);
68266832

68276833
$type = $relationship->getAttribute('options')['relationType'];
68286834
$side = $relationship->getAttribute('options')['side'];
@@ -6850,6 +6856,7 @@ private function processRelationshipQueries(
68506856
break;
68516857
}
68526858
}
6859+
68536860
$query->setValues(\array_values($values));
68546861
}
68556862

tests/e2e/Adapter/Scopes/RelationshipTests.php

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,217 @@ trait RelationshipTests
2727
use ManyToOneTests;
2828
use ManyToManyTests;
2929

30+
public function testZoo(): void
31+
{
32+
/** @var Database $database */
33+
$database = static::getDatabase();
34+
35+
if (!$database->getAdapter()->getSupportForRelationships()) {
36+
$this->expectNotToPerformAssertions();
37+
return;
38+
}
39+
40+
$database->createCollection('zoo');
41+
$database->createAttribute('zoo', 'name', Database::VAR_STRING, 256, true);
42+
43+
$database->createCollection('veterinarians');
44+
$database->createAttribute('veterinarians', 'fullname', Database::VAR_STRING, 256, true);
45+
46+
$database->createCollection('presidents');
47+
$database->createAttribute('presidents', 'first_name', Database::VAR_STRING, 256, true);
48+
$database->createAttribute('presidents', 'last_name', Database::VAR_STRING, 256, true);
49+
$database->createRelationship(
50+
collection: 'presidents',
51+
relatedCollection: 'veterinarians',
52+
type: Database::RELATION_MANY_TO_MANY,
53+
twoWay: true,
54+
id: 'votes',
55+
twoWayKey: 'presidents'
56+
);
57+
58+
$database->createCollection('__animals');
59+
$database->createAttribute('__animals', 'name', Database::VAR_STRING, 256, true);
60+
$database->createAttribute('__animals', 'age', Database::VAR_INTEGER, 0, false);
61+
$database->createAttribute('__animals', 'price', Database::VAR_FLOAT, 0, false);
62+
$database->createAttribute('__animals', 'date_of_birth', Database::VAR_DATETIME, 0, true, filters:['datetime']);
63+
$database->createAttribute('__animals', 'longtext', Database::VAR_STRING, 100000000, false);
64+
$database->createAttribute('__animals', 'is_active', Database::VAR_BOOLEAN, 0, false, default: true);
65+
$database->createAttribute('__animals', 'integers', Database::VAR_INTEGER, 0, false, array: true);
66+
$database->createAttribute('__animals', 'email', Database::VAR_STRING, 255, false);
67+
$database->createAttribute('__animals', 'ip', Database::VAR_STRING, 255, false);
68+
$database->createAttribute('__animals', 'url', Database::VAR_STRING, 255, false);
69+
$database->createAttribute('__animals', 'enum', Database::VAR_STRING, 255, false);
70+
71+
$database->createRelationship(
72+
collection: 'presidents',
73+
relatedCollection: '__animals',
74+
type: Database::RELATION_ONE_TO_ONE,
75+
twoWay: true,
76+
id: 'animal',
77+
twoWayKey: 'president'
78+
);
79+
80+
$database->createRelationship(
81+
collection: 'veterinarians',
82+
relatedCollection: '__animals',
83+
type: Database::RELATION_ONE_TO_MANY,
84+
twoWay: true,
85+
id: 'animals',
86+
twoWayKey: 'veterinarian'
87+
);
88+
89+
$database->createRelationship(
90+
collection: '__animals',
91+
relatedCollection: 'zoo',
92+
type: Database::RELATION_MANY_TO_ONE,
93+
twoWay: true,
94+
id: 'zoo',
95+
twoWayKey: 'animals'
96+
);
97+
98+
$zoo1 = $database->createDocument('zoo', new Document([
99+
'$id' => 'zoo1',
100+
'$permissions' => [
101+
Permission::read(Role::any()),
102+
Permission::update(Role::any()),
103+
],
104+
'name' => 'Bronx Zoo'
105+
]));
106+
107+
$animal1 = $database->createDocument('__animals', new Document([
108+
'$id' => 'iguana',
109+
'$permissions' => [
110+
Permission::read(Role::any()),
111+
Permission::update(Role::any()),
112+
],
113+
'name' => 'Iguana',
114+
'age' => 11,
115+
'price' => 50.5,
116+
'date_of_birth' => '1975-06-12',
117+
'longtext' => 'I am a pretty long text',
118+
'is_active' => true,
119+
'integers' => [1, 2, 3],
120+
'email' => 'iguana@appwrite.io',
121+
'enum' => 'maybe',
122+
'ip' => '127.0.0.1',
123+
'url' => 'https://appwrite.io/',
124+
'zoo' => $zoo1->getId(),
125+
]));
126+
127+
$animal2 = $database->createDocument('__animals', new Document([
128+
'$id' => 'tiger',
129+
'$permissions' => [
130+
Permission::read(Role::any()),
131+
Permission::update(Role::any()),
132+
],
133+
'name' => 'Tiger',
134+
'age' => 5,
135+
'price' => 1000,
136+
'date_of_birth' => '2020-06-12',
137+
'longtext' => 'I am a hungry tiger',
138+
'is_active' => false,
139+
'integers' => [9, 2, 3],
140+
'email' => 'tiger@appwrite.io',
141+
'enum' => 'yes',
142+
'ip' => '255.0.0.1',
143+
'url' => 'https://appwrite.io/',
144+
'zoo' => $zoo1->getId(),
145+
]));
146+
147+
$animal3 = $database->createDocument('__animals', new Document([
148+
'$id' => 'lama',
149+
'$permissions' => [
150+
Permission::read(Role::any()),
151+
Permission::update(Role::any()),
152+
],
153+
'name' => 'Lama',
154+
'age' => 15,
155+
'price' => 1000,
156+
'date_of_birth' => '1975-06-12',
157+
'is_active' => true,
158+
'integers' => null,
159+
'email' => null,
160+
'enum' => null,
161+
'ip' => '255.0.0.1',
162+
'url' => 'https://appwrite.io/',
163+
'zoo' => null,
164+
]));
165+
166+
$veterinarian1 = $database->createDocument('veterinarians', new Document([
167+
'$id' => 'dr.pol',
168+
'$permissions' => [
169+
Permission::read(Role::any()),
170+
Permission::update(Role::any()),
171+
],
172+
'fullname' => 'The Incredible Dr. Pol',
173+
'animals' => ['iguana'],
174+
]));
175+
176+
$veterinarian2 = $database->createDocument('veterinarians', new Document([
177+
'$id' => 'dr.seuss',
178+
'$permissions' => [
179+
Permission::read(Role::any()),
180+
Permission::update(Role::any()),
181+
],
182+
'fullname' => 'Dr. Seuss',
183+
'animals' => ['tiger'],
184+
]));
185+
186+
$president1 = $database->createDocument('presidents', new Document([
187+
'$id' => 'trump',
188+
'$permissions' => [
189+
Permission::read(Role::any()),
190+
Permission::update(Role::any()),
191+
],
192+
'first_name' => 'Donald',
193+
'last_name' => 'Trump',
194+
'votes' => [
195+
$veterinarian1->getId(),
196+
$veterinarian2->getId(),
197+
],
198+
]));
199+
200+
$president2 = $database->createDocument('presidents', new Document([
201+
'$id' => 'bush',
202+
'$permissions' => [
203+
Permission::read(Role::any()),
204+
Permission::update(Role::any()),
205+
],
206+
'first_name' => 'George',
207+
'last_name' => 'Bush',
208+
'animal' => 'iguana',
209+
]));
210+
211+
$president3 = $database->createDocument('presidents', new Document([
212+
'$id' => 'biden',
213+
'$permissions' => [
214+
Permission::read(Role::any()),
215+
Permission::update(Role::any()),
216+
],
217+
'first_name' => 'Joe',
218+
'last_name' => 'Biden',
219+
'animal' => 'tiger',
220+
]));
221+
222+
var_dump('=== start === === start === === start === === start === === start === === start === === start === === start === === start ===');
223+
224+
$docs = $database->find(
225+
'veterinarians',
226+
[
227+
Query::select([
228+
'*',
229+
'animals.*',
230+
'animals.zoo.*',
231+
//'animals.president.*',
232+
])
233+
]
234+
);
235+
236+
var_dump($docs);
237+
238+
//$this->assertEquals('shmuel', 'fogel');
239+
}
240+
30241
public function testDeleteRelatedCollection(): void
31242
{
32243
/** @var Database $database */

0 commit comments

Comments
 (0)