diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index a2f8854f..41c35c28 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -248,28 +248,7 @@ class ApiController extends Controller ); // Preload all tag previews - $previews = $this->timelineQuery->getTagPreviews($folder); - - // Convert to map with key as systemtagid - $previews_map = []; - foreach ($previews as &$preview) { - $key = $preview['systemtagid']; - if (!\array_key_exists($key, $previews_map)) { - $previews_map[$key] = []; - } - unset($preview['systemtagid']); - $previews_map[$key][] = $preview; - } - - // Add previews to list - foreach ($list as &$tag) { - $key = $tag['id']; - if (\array_key_exists($key, $previews_map)) { - $tag['previews'] = $previews_map[$key]; - } else { - $tag['previews'] = []; - } - } + $this->timelineQuery->getTagPreviews($list, $folder); return new JSONResponse($list, Http::STATUS_OK); } diff --git a/lib/Db/TimelineQueryDays.php b/lib/Db/TimelineQueryDays.php index 82a61d90..87b2bc93 100644 --- a/lib/Db/TimelineQueryDays.php +++ b/lib/Db/TimelineQueryDays.php @@ -159,34 +159,33 @@ trait TimelineQueryDays return $day; } - /** Get the query for oc_filecache join */ - private function getFilecacheJoinQuery( - IQueryBuilder &$query, + /** + * Get all folders inside a top folder + */ + private function getSubfolderIdsRecursive( + IDBConnection &$conn, Folder &$folder, - bool $recursive, bool $archive ) { - $pathQuery = null; - if ($recursive) { - // CTE to get all folders recursively in the given top folder - $cte = - 'WITH RECURSIVE cte_folders(fileid) AS ( - SELECT - f.fileid - FROM - *PREFIX*filecache f - WHERE - f.fileid = :topFolderId - UNION ALL - SELECT - f.fileid - FROM - *PREFIX*filecache f - INNER JOIN cte_folders c - ON (f.parent = c.fileid - AND f.mimetype = 2 - AND f.fileid NOT IN (:excludedFolderIds) - ) + // CTE to get all folders recursively in the given top folder + $cte = + 'WITH RECURSIVE cte_folders(fileid) AS ( + SELECT + f.fileid + FROM + *PREFIX*filecache f + WHERE + f.fileid = :topFolderId + UNION ALL + SELECT + f.fileid + FROM + *PREFIX*filecache f + INNER JOIN cte_folders c + ON (f.parent = c.fileid + AND f.mimetype = 2 + AND f.fileid NOT IN (:excludedFolderIds) + ) ) SELECT fileid @@ -194,34 +193,60 @@ trait TimelineQueryDays cte_folders '; - // Query parameters, set at the end - $topFolderId = $folder->getId(); - $excludedFolderIds = [-1]; // cannot be empty + // Query parameters, set at the end + $topFolderId = $folder->getId(); + $excludedFolderIds = [-1]; // cannot be empty - /** @var Folder Archive folder if it exists */ - $archiveFolder = null; + /** @var Folder Archive folder if it exists */ + $archiveFolder = null; - try { - $archiveFolder = $folder->get('.archive/'); - } catch (\OCP\Files\NotFoundException $e) { + try { + $archiveFolder = $folder->get('.archive/'); + } catch (\OCP\Files\NotFoundException $e) { + } + + if (!$archive) { + // Exclude archive folder + if ($archiveFolder) { + $excludedFolderIds[] = $archiveFolder->getId(); } + } else { + // Only include archive folder + $topFolderId = $archiveFolder ? $archiveFolder->getId() : -1; + } - if (!$archive) { - // Exclude archive folder - if ($archiveFolder) { - $excludedFolderIds[] = $archiveFolder->getId(); - } + return array_column($conn->executeQuery($cte, [ + 'topFolderId' => $topFolderId, + 'excludedFolderIds' => $excludedFolderIds, + ])->fetchAll(), 'fileid'); + } + + /** + * Get the query for oc_filecache join + * + * @param IQueryBuilder $query Query builder + * @param Folder|array $folder Either the top folder or array of folder Ids + * @param bool $recursive Whether to get the days recursively + * @param bool $archive Whether to get the days only from the archive folder + */ + private function getFilecacheJoinQuery( + IQueryBuilder &$query, + &$folder, + bool $recursive, + bool $archive + ) { + $pathQuery = null; + if ($recursive) { + // Get all subfolder Ids recursively + $folderIds = []; + if ($folder instanceof Folder) { + $folderIds = $this->getSubfolderIdsRecursive($query->getConnection(), $folder, $archive); } else { - // Only include archive folder - $topFolderId = $archiveFolder ? $archiveFolder->getId() : -1; + $folderIds = $folder; } - // Join with CTE - $pathQuery = $query->expr()->in('f.parent', $query->createFunction($cte)); - - // Set query parameters - $query->setParameter('topFolderId', $topFolderId, IQueryBuilder::PARAM_INT); - $query->setParameter('excludedFolderIds', $excludedFolderIds, IQueryBuilder::PARAM_INT_ARRAY); + // Join with folder IDs + $pathQuery = $query->expr()->in('f.parent', $query->createNamedParameter($folderIds, IQueryBuilder::PARAM_INT_ARRAY)); } else { // If getting non-recursively folder only check for parent $pathQuery = $query->expr()->eq('f.parent', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT)); diff --git a/lib/Db/TimelineQueryTags.php b/lib/Db/TimelineQueryTags.php index d5a82d48..80af1d8c 100644 --- a/lib/Db/TimelineQueryTags.php +++ b/lib/Db/TimelineQueryTags.php @@ -77,47 +77,39 @@ trait TimelineQueryTags return $tags; } - public function getTagPreviews(Folder $folder) + public function getTagPreviews(array &$tags, Folder &$folder) { - $query = $this->connection->getQueryBuilder(); + // Cache subfolder ids to prevent duplicate requests + $folderIds = $this->getSubfolderIdsRecursive($this->connection, $folder, false); - // Windowing - $rowNumber = $query->createFunction('ROW_NUMBER() OVER (PARTITION BY stom.systemtagid) as n'); + foreach ($tags as &$tag) { + $query = $this->connection->getQueryBuilder(); - // SELECT all photos with this tag - $query->select('f.fileid', 'f.etag', 'stom.systemtagid', $rowNumber)->from( - 'systemtag_object_mapping', - 'stom' - )->where( - $query->expr()->eq('stom.objecttype', $query->createNamedParameter('files')), - ); + // SELECT all photos with this tag + $query->select('f.fileid', 'f.etag')->from( + 'systemtag_object_mapping', + 'stom' + )->where( + $query->expr()->eq('stom.objecttype', $query->createNamedParameter('files')), + $query->expr()->eq('stom.systemtagid', $query->createNamedParameter($tag['id'])), + ); - // WHERE these items are memories indexed photos - $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid')); + // WHERE these items are memories indexed photos + $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid')); - // WHERE these photos are in the user's requested folder recursively - $query->innerJoin('m', 'filecache', 'f', $this->getFilecacheJoinQuery($query, $folder, true, false)); + // WHERE these photos are in the user's requested folder recursively + $query->innerJoin('m', 'filecache', 'f', $this->getFilecacheJoinQuery($query, $folderIds, true, false)); - // Make this a sub query - $fun = $query->createFunction('('.$query->getSQL().')'); + // MAX 4 + $query->setMaxResults(4); - // Create outer query - $outerQuery = $this->connection->getQueryBuilder(); - $outerQuery->setParameters($query->getParameters()); - $outerQuery->select('*')->from($fun, 't'); - $outerQuery->where($query->expr()->lte('t.n', $outerQuery->createParameter('nc'))); - $outerQuery->setParameter('nc', 4, IQueryBuilder::PARAM_INT); + // FETCH tag previews + $tag['previews'] = $query->executeQuery()->fetchAll(); - // FETCH all tag previews - $previews = $outerQuery->executeQuery()->fetchAll(); - - // Post-process - foreach ($previews as &$row) { - $row['fileid'] = (int) $row['fileid']; - $row['systemtagid'] = (int) $row['systemtagid']; - unset($row['n']); + // Post-process + foreach ($tag['previews'] as &$row) { + $row['fileid'] = (int) $row['fileid']; + } } - - return $previews; } }