Skip to content
Prev Previous commit
Next Next commit
handle limit and offset in folder file search
Signed-off-by: Robin Appelman <[email protected]>
  • Loading branch information
icewind1991 committed Mar 18, 2021
commit bd229e7548e86d12899dc0246272438dbe528edf
57 changes: 51 additions & 6 deletions lib/private/Files/Node/Folder.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,8 @@ public function newFile($path, $content = null) {
}

private function queryFromOperator(ISearchOperator $operator, string $uid = null): ISearchQuery {
if (!$uid) {
/** @var IUserSession $session */
$session = \OC::$server->query(IUserSession::class);
$user = $session->getUser();
if ($uid === null) {
$user = null;
} else {
/** @var IUserManager $userManager */
$userManager = \OC::$server->query(IUserManager::class);
Expand All @@ -230,11 +228,35 @@ public function search($query) {
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
}

// assume a setup where the root mount matches 15 items,
// sub mount1 matches 7 items and mount2 matches 1 item
// a search with (0..10) returns 10 results from root with internal offset 0 and limit 10
// follow up search with (10..20) returns 5 results from root with internal offset 10 and limit 10
// and 5 results from mount1 with internal offset 0 and limit 5
// next search with (20..30) return 0 results from root with internal offset 20 and limit 10
// 2 results from mount1 with internal offset 5[1] and limit 5
// and 1 result from mount2 with internal offset 0[1] and limit 8
//
// Because of the difficulty of calculating the offset for a sub-query if the previous one returns no results
// (we don't know how many results the previous sub-query has skipped with it's own offset)
// we instead discard the offset for the sub-queries and filter it afterwards and add the offset to limit.
// this is sub-optimal but shouldn't hurt to much since large offsets are uncommon in practice

$limitToHome = $query->limitToHome();
if ($limitToHome && count(explode('/', $this->path)) !== 3) {
throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
}

$subQueryLimit = $query->getLimit() > 0 ? $query->getLimit() + $query->getOffset() : PHP_INT_MAX;
$subQueryOffset = $query->getOffset();
$noLimitQuery = new SearchQuery(
$query->getSearchOperation(),
$subQueryLimit,
0,
$query->getOrder(),
$query->getUser()
);

$files = [];
$rootLength = strlen($this->path);
$mount = $this->root->getMount($this->path);
Expand All @@ -248,7 +270,12 @@ public function search($query) {

$cache = $storage->getCache('');

$results = $cache->searchQuery($query);
$results = $cache->searchQuery($noLimitQuery);
$count = count($results);
$results = array_slice($results, $subQueryOffset, $subQueryLimit);
$subQueryOffset = max(0, $subQueryOffset - $count);
$subQueryLimit = max(0, $subQueryLimit - $count);

foreach ($results as $result) {
if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
$result['internalPath'] = $result['path'];
Expand All @@ -261,12 +288,30 @@ public function search($query) {
if (!$limitToHome) {
$mounts = $this->root->getMountsIn($this->path);
foreach ($mounts as $mount) {
if ($subQueryLimit <= 0) {
break;
}

$subQuery = new SearchQuery(
$query->getSearchOperation(),
$subQueryLimit,
0,
$query->getOrder(),
$query->getUser()
);

$storage = $mount->getStorage();
if ($storage) {
$cache = $storage->getCache('');

$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
$results = $cache->searchQuery($query);
$results = $cache->searchQuery($subQuery);

$count = count($results);
$results = array_slice($results, $subQueryOffset, $subQueryLimit);
$subQueryOffset -= $count;
$subQueryLimit -= $count;

foreach ($results as $result) {
$result['internalPath'] = $result['path'];
$result['path'] = $relativeMountPoint . $result['path'];
Expand Down
Loading