Skip to content

Commit fd24f36

Browse files
authored
Merge pull request #632 from utopia-php/fix-relationship-select
Fix relationship select
2 parents ad7613a + f72e855 commit fd24f36

2 files changed

Lines changed: 141 additions & 86 deletions

File tree

src/Database/Database.php

Lines changed: 76 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3221,43 +3221,7 @@ public function getDocument(string $collection, string $id, array $queries = [],
32213221

32223222
$selects = Query::groupByType($queries)['selections'];
32233223
$selections = $this->validateSelections($collection, $selects);
3224-
$nestedSelections = [];
3225-
3226-
foreach ($queries as $query) {
3227-
if ($query->getMethod() == Query::TYPE_SELECT) {
3228-
$values = $query->getValues();
3229-
foreach ($values as $valueIndex => $value) {
3230-
if (\str_contains($value, '.')) {
3231-
// Shift the top level off the dot-path to pass the selection down the chain
3232-
// 'foo.bar.baz' becomes 'bar.baz'
3233-
$nestedSelections[] = Query::select([
3234-
\implode('.', \array_slice(\explode('.', $value), 1))
3235-
]);
3236-
3237-
$key = \explode('.', $value)[0];
3238-
3239-
foreach ($relationships as $relationship) {
3240-
if ($relationship->getAttribute('key') === $key) {
3241-
switch ($relationship->getAttribute('options')['relationType']) {
3242-
case Database::RELATION_MANY_TO_MANY:
3243-
case Database::RELATION_ONE_TO_MANY:
3244-
unset($values[$valueIndex]);
3245-
break;
3246-
3247-
case Database::RELATION_MANY_TO_ONE:
3248-
case Database::RELATION_ONE_TO_ONE:
3249-
$values[$valueIndex] = $key;
3250-
break;
3251-
}
3252-
}
3253-
}
3254-
}
3255-
}
3256-
$query->setValues(\array_values($values));
3257-
}
3258-
}
3259-
3260-
$queries = \array_values($queries);
3224+
$nestedSelections = $this->processRelationshipQueries($relationships, $queries);
32613225

32623226
$validator = new Authorization(self::PERMISSION_READ);
32633227
$documentSecurity = $collection->getAttribute('documentSecurity', false);
@@ -6061,50 +6025,7 @@ public function find(string $collection, array $queries = [], string $forPermiss
60616025
);
60626026

60636027
$selections = $this->validateSelections($collection, $selects);
6064-
$nestedSelections = [];
6065-
6066-
foreach ($queries as $index => &$query) {
6067-
switch ($query->getMethod()) {
6068-
case Query::TYPE_SELECT:
6069-
$values = $query->getValues();
6070-
foreach ($values as $valueIndex => $value) {
6071-
if (\str_contains($value, '.')) {
6072-
// Shift the top level off the dot-path to pass the selection down the chain
6073-
// 'foo.bar.baz' becomes 'bar.baz'
6074-
$nestedSelections[] = Query::select([
6075-
\implode('.', \array_slice(\explode('.', $value), 1))
6076-
]);
6077-
6078-
$key = \explode('.', $value)[0];
6079-
6080-
foreach ($relationships as $relationship) {
6081-
if ($relationship->getAttribute('key') === $key) {
6082-
switch ($relationship->getAttribute('options')['relationType']) {
6083-
case Database::RELATION_MANY_TO_MANY:
6084-
case Database::RELATION_ONE_TO_MANY:
6085-
unset($values[$valueIndex]);
6086-
break;
6087-
6088-
case Database::RELATION_MANY_TO_ONE:
6089-
case Database::RELATION_ONE_TO_ONE:
6090-
$values[$valueIndex] = $key;
6091-
break;
6092-
}
6093-
}
6094-
}
6095-
}
6096-
}
6097-
$query->setValues(\array_values($values));
6098-
break;
6099-
default:
6100-
if (\str_contains($query->getAttribute(), '.')) {
6101-
unset($queries[$index]);
6102-
}
6103-
break;
6104-
}
6105-
}
6106-
6107-
$queries = \array_values($queries);
6028+
$nestedSelections = $this->processRelationshipQueries($relationships, $queries);
61086029

61096030
$getResults = fn () => $this->adapter->find(
61106031
$collection->getId(),
@@ -6133,8 +6054,6 @@ public function find(string $collection, array $queries = [], string $forPermiss
61336054
}
61346055
}
61356056

6136-
unset($query);
6137-
61386057
$this->trigger(self::EVENT_DOCUMENT_FIND, $results);
61396058

61406059
return $results;
@@ -6786,7 +6705,7 @@ public function getCacheKeys(string $collectionId, ?string $documentId = null, a
67866705
* @return void
67876706
* @throws QueryException
67886707
*/
6789-
public function checkQueriesType(array $queries)
6708+
private function checkQueriesType(array $queries): void
67906709
{
67916710
foreach ($queries as $query) {
67926711
if (!$query instanceof Query) {
@@ -6798,4 +6717,77 @@ public function checkQueriesType(array $queries)
67986717
}
67996718
}
68006719
}
6720+
6721+
/**
6722+
* Process relationship queries, extracting nested selections.
6723+
*
6724+
* @param array<Document> $relationships
6725+
* @param array<Query> $queries
6726+
* @return array<Query>
6727+
*/
6728+
private function processRelationshipQueries(
6729+
array $relationships,
6730+
array $queries,
6731+
): array {
6732+
$nestedSelections = [];
6733+
6734+
foreach ($queries as $query) {
6735+
if ($query->getMethod() !== Query::TYPE_SELECT) {
6736+
continue;
6737+
}
6738+
6739+
$values = $query->getValues();
6740+
foreach ($values as $valueIndex => $value) {
6741+
if (!\str_contains($value, '.')) {
6742+
continue;
6743+
}
6744+
6745+
$selectedKey = \explode('.', $value)[0];
6746+
6747+
$relationship = \array_values(\array_filter(
6748+
$relationships,
6749+
fn (Document $relationship) => $relationship->getAttribute('key') === $selectedKey,
6750+
))[0] ?? null;
6751+
6752+
if (!$relationship) {
6753+
continue;
6754+
}
6755+
6756+
// Shift the top level off the dot-path to pass the selection down the chain
6757+
// 'foo.bar.baz' becomes 'bar.baz'
6758+
$nestedSelections[] = Query::select([
6759+
\implode('.', \array_slice(\explode('.', $value), 1))
6760+
]);
6761+
6762+
$type = $relationship->getAttribute('options')['relationType'];
6763+
$side = $relationship->getAttribute('options')['side'];
6764+
6765+
switch ($type) {
6766+
case Database::RELATION_MANY_TO_MANY:
6767+
unset($values[$valueIndex]);
6768+
break;
6769+
case Database::RELATION_ONE_TO_MANY:
6770+
if ($side === Database::RELATION_SIDE_PARENT) {
6771+
unset($values[$valueIndex]);
6772+
} else {
6773+
$values[$valueIndex] = $selectedKey;
6774+
}
6775+
break;
6776+
case Database::RELATION_MANY_TO_ONE:
6777+
if ($side === Database::RELATION_SIDE_PARENT) {
6778+
$values[$valueIndex] = $selectedKey;
6779+
} else {
6780+
unset($values[$valueIndex]);
6781+
}
6782+
break;
6783+
case Database::RELATION_ONE_TO_ONE:
6784+
$values[$valueIndex] = $selectedKey;
6785+
break;
6786+
}
6787+
}
6788+
$query->setValues(\array_values($values));
6789+
}
6790+
6791+
return $nestedSelections;
6792+
}
68016793
}

tests/e2e/Adapter/Scopes/RelationshipTests.php

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,14 +931,17 @@ public function testSelectRelationshipAttributes(): void
931931
$database->createCollection('model');
932932

933933
$database->createAttribute('make', 'name', Database::VAR_STRING, 255, true);
934+
$database->createAttribute('make', 'origin', Database::VAR_STRING, 255, true);
934935
$database->createAttribute('model', 'name', Database::VAR_STRING, 255, true);
935936
$database->createAttribute('model', 'year', Database::VAR_INTEGER, 0, true);
936937

937938
$database->createRelationship(
938939
collection: 'make',
939940
relatedCollection: 'model',
940941
type: Database::RELATION_ONE_TO_MANY,
941-
id: 'models'
942+
twoWay: true,
943+
id: 'models',
944+
twoWayKey: 'make',
942945
);
943946

944947
$database->createDocument('make', new Document([
@@ -947,6 +950,7 @@ public function testSelectRelationshipAttributes(): void
947950
Permission::read(Role::any()),
948951
],
949952
'name' => 'Ford',
953+
'origin' => 'USA',
950954
'models' => [
951955
[
952956
'$id' => 'fiesta',
@@ -1143,9 +1147,68 @@ public function testSelectRelationshipAttributes(): void
11431147
if ($make->isEmpty()) {
11441148
throw new Exception('Make not found');
11451149
}
1146-
11471150
$this->assertEquals('Ford', $make['name']);
11481151
$this->assertArrayNotHasKey('models', $make);
1152+
1153+
// Select some parent attributes, all child attributes
1154+
$make = $database->findOne('make', [
1155+
Query::select(['name', 'models.*']),
1156+
]);
1157+
1158+
$this->assertEquals('Ford', $make['name']);
1159+
$this->assertEquals(2, \count($make['models']));
1160+
1161+
/*
1162+
* FROM CHILD TO PARENT
1163+
*/
1164+
1165+
// Select some parent attributes, some child attributes
1166+
$model = $database->findOne('model', [
1167+
Query::select(['name', 'make.name']),
1168+
]);
1169+
1170+
$this->assertEquals('Fiesta', $model['name']);
1171+
$this->assertEquals('Ford', $model['make']['name']);
1172+
$this->assertArrayNotHasKey('origin', $model['make']);
1173+
$this->assertArrayNotHasKey('year', $model);
1174+
$this->assertArrayHasKey('name', $model);
1175+
1176+
// Select all parent attributes, some child attributes
1177+
$model = $database->findOne('model', [
1178+
Query::select(['*', 'make.name']),
1179+
]);
1180+
1181+
$this->assertEquals('Fiesta', $model['name']);
1182+
$this->assertEquals('Ford', $model['make']['name']);
1183+
$this->assertArrayHasKey('year', $model);
1184+
1185+
// Select all parent attributes, all child attributes
1186+
$model = $database->findOne('model', [
1187+
Query::select(['*', 'make.*']),
1188+
]);
1189+
1190+
$this->assertEquals('Fiesta', $model['name']);
1191+
$this->assertEquals('Ford', $model['make']['name']);
1192+
$this->assertArrayHasKey('year', $model);
1193+
$this->assertArrayHasKey('name', $model['make']);
1194+
1195+
// Select all parent attributes, no child attributes
1196+
$model = $database->findOne('model', [
1197+
Query::select(['*']),
1198+
]);
1199+
1200+
$this->assertEquals('Fiesta', $model['name']);
1201+
$this->assertArrayHasKey('make', $model);
1202+
$this->assertArrayHasKey('year', $model);
1203+
1204+
// Select some parent attributes, all child attributes
1205+
$model = $database->findOne('model', [
1206+
Query::select(['name', 'make.*']),
1207+
]);
1208+
1209+
$this->assertEquals('Fiesta', $model['name']);
1210+
$this->assertEquals('Ford', $model['make']['name']);
1211+
$this->assertEquals('USA', $model['make']['origin']);
11491212
}
11501213

11511214
public function testInheritRelationshipPermissions(): void

0 commit comments

Comments
 (0)