mirror of
https://github.com/appwrite/appwrite
synced 2026-05-22 16:38:32 +00:00
Fix request filter for nested relationships
This commit is contained in:
parent
8a76979ba8
commit
3fc53b2c43
2 changed files with 60 additions and 38 deletions
12
composer.lock
generated
12
composer.lock
generated
|
|
@ -3557,16 +3557,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "99beaf1dd6dc3561c8332f9893325777553644a4"
|
||||
"reference": "8a536fead840d9da6ee819fe6b80e0f047997f69"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/99beaf1dd6dc3561c8332f9893325777553644a4",
|
||||
"reference": "99beaf1dd6dc3561c8332f9893325777553644a4",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/8a536fead840d9da6ee819fe6b80e0f047997f69",
|
||||
"reference": "8a536fead840d9da6ee819fe6b80e0f047997f69",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -3607,9 +3607,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/1.2.1"
|
||||
"source": "https://github.com/utopia-php/database/tree/1.2.3"
|
||||
},
|
||||
"time": "2025-08-26T16:05:26+00:00"
|
||||
"time": "2025-08-27T11:47:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
|
|
|
|||
|
|
@ -32,59 +32,67 @@ class V20 extends Filter
|
|||
*/
|
||||
protected function manageSelectQueries(array $content): array
|
||||
{
|
||||
$hasWildcard = false;
|
||||
if (!isset($content['queries'])) {
|
||||
$hasWildcard = true;
|
||||
// only query, make it json encoded!
|
||||
$content['queries'] = [Query::select(['*'])->toString()];
|
||||
$content['queries'] = [];
|
||||
}
|
||||
|
||||
// Handle case where queries is an array but empty
|
||||
if (\is_array($content['queries'])) {
|
||||
$content['queries'] = \array_filter($content['queries'], function($q) {
|
||||
if (\is_object($q) && empty((array)$q)) {
|
||||
return false;
|
||||
}
|
||||
if (\is_string($q) && \trim($q) === '') {
|
||||
return false;
|
||||
}
|
||||
if (empty($q)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
$parsed = Query::parseQueries($content['queries']);
|
||||
} catch (QueryException) {
|
||||
// don't crash!
|
||||
return $content;
|
||||
}
|
||||
|
||||
$selections = Query::groupByType($parsed)['selections'] ?? [];
|
||||
|
||||
// If there are no select queries at all, add wildcard
|
||||
if (empty($selections)) {
|
||||
$hasWildcard = true;
|
||||
$parsed[] = Query::select(['*']);
|
||||
} elseif (!$hasWildcard) {
|
||||
// check if any select includes a wildcard as we added one above
|
||||
|
||||
// Check if we need to add wildcard + relationships
|
||||
// This happens when:
|
||||
// 1. No select queries exist, OR
|
||||
// 2. A wildcard select exists
|
||||
$needsRelationships = empty($selections);
|
||||
if (!$needsRelationships) {
|
||||
foreach ($selections as $select) {
|
||||
if (\in_array('*', $select->getValues(), true)) {
|
||||
$hasWildcard = true;
|
||||
$needsRelationships = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add `keys.*` for all model types!
|
||||
* Add wildcard and relationship selects for backward compatibility
|
||||
*/
|
||||
if ($hasWildcard) {
|
||||
if ($needsRelationships) {
|
||||
$relatedKeys = $this->getRelatedCollectionKeys();
|
||||
$selects = \array_values(\array_unique(\array_merge(['*'], $relatedKeys)));
|
||||
|
||||
if (! empty($relatedKeys)) {
|
||||
$selects = \array_values(\array_unique(\array_merge(['*'], $relatedKeys)));
|
||||
// Remove any existing select queries
|
||||
$parsed = \array_filter(
|
||||
$parsed,
|
||||
fn ($query) => $query->getMethod() !== Query::TYPE_SELECT
|
||||
);
|
||||
|
||||
// remove previous select queries
|
||||
$parsed = \array_filter(
|
||||
$parsed,
|
||||
fn ($query) => $query->getMethod() !== Query::TYPE_SELECT
|
||||
);
|
||||
|
||||
// add wildcard + relationship(s) selects
|
||||
$parsed[] = Query::select($selects);
|
||||
}
|
||||
// Add wildcard + relationship(s) selects
|
||||
$parsed[] = Query::select($selects);
|
||||
}
|
||||
|
||||
$resolvedQueries = [];
|
||||
foreach ($parsed as $query) {
|
||||
// make em json encoded!
|
||||
$resolvedQueries[] = $query->toString();
|
||||
}
|
||||
|
||||
|
|
@ -95,12 +103,15 @@ class V20 extends Filter
|
|||
|
||||
/**
|
||||
* Returns all relationship attribute keys in `key.*` format for use with `Query::select`.
|
||||
* Recursively includes nested relationships up to 3 levels deep.
|
||||
* Prevents infinite loops by tracking all visited collections in the current path.
|
||||
*/
|
||||
private function getRelatedCollectionKeys(
|
||||
?string $databaseId = null,
|
||||
?string $collectionId = null,
|
||||
?string $prefix = null,
|
||||
int $depth = 1,
|
||||
array $visited = []
|
||||
): array {
|
||||
$databaseId ??= $this->getParamValue('databaseId');
|
||||
$collectionId ??= $this->getParamValue('collectionId');
|
||||
|
|
@ -112,6 +123,13 @@ class V20 extends Filter
|
|||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check if we've already visited this collection in the current path to prevent cycles
|
||||
if (in_array($collectionId, $visited)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$visited[] = $collectionId;
|
||||
|
||||
$dbForProject = $this->getDbForProject();
|
||||
if ($dbForProject === null) {
|
||||
|
|
@ -144,23 +162,27 @@ class V20 extends Filter
|
|||
|
||||
$key = $attr['key'];
|
||||
$fullKey = $prefix ? $prefix . '.' . $key : $key;
|
||||
$relatedCollectionId = $attr['relatedCollection'] ?? null;
|
||||
|
||||
// Skip this relationship entirely if it points to an already visited collection
|
||||
if ($relatedCollectionId && in_array($relatedCollectionId, $visited)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the wildcard select for this relationship
|
||||
$relationshipKeys[] = $fullKey . '.*';
|
||||
|
||||
// Get the related collection for nested relationships
|
||||
$relatedCollectionId = $attr['relatedCollection'] ?? null;
|
||||
|
||||
// Continue recursively if we have a related collection
|
||||
if ($relatedCollectionId) {
|
||||
// Recursively get nested relationship keys
|
||||
$nestedKeys = $this->getRelatedCollectionKeys(
|
||||
$databaseId,
|
||||
$relatedCollectionId,
|
||||
$fullKey,
|
||||
$depth + 1,
|
||||
$visited
|
||||
);
|
||||
|
||||
$relationshipKeys = \array_merge($relationshipKeys, $nestedKeys);
|
||||
\array_push($relationshipKeys, ...$nestedKeys);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue