Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand All @@ -3310,11 +3310,11 @@ public function getDocument(string $collection, string $id, array $queries = [],
/**
* @param Document $collection
* @param Document $document
* @param array<Query> $queries
* @param array<string, array<Query>> $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', []);

Expand All @@ -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) {
Expand Down Expand Up @@ -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));
}

Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -6783,7 +6788,7 @@ private function checkQueriesType(array $queries): void
*
* @param array<Document> $relationships
* @param array<Query> $queries
* @return array<Query>
* @return array<string, array<Query>> $selects
*/
private function processRelationshipQueries(
array $relationships,
Expand All @@ -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,
Expand All @@ -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'];
Expand Down Expand Up @@ -6845,6 +6851,7 @@ private function processRelationshipQueries(
break;
}
}

$query->setValues(\array_values($values));
}

Expand Down
211 changes: 211 additions & 0 deletions tests/e2e/Adapter/Scopes/RelationshipTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove debug output.

The var_dump($docs) statement should be removed as it's debug code.

-        var_dump($docs);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var_dump($docs);
// Add meaningful assertions here to validate the nested relationship query results
$this->assertNotEmpty($docs);
// TODO: Add specific assertions to validate the structure and content of returned documents
🤖 Prompt for AI Agents
In tests/e2e/Adapter/Scopes/RelationshipTests.php at line 236, remove the
var_dump($docs) statement as it is debug output and should not be present in the
final test code.


//$this->assertEquals('shmuel', 'fogel');
}

public function testDeleteRelatedCollection(): void
{
/** @var Database $database */
Expand Down