diff --git a/lib/Analytics/AnalyticsDatasource.php b/lib/Analytics/AnalyticsDatasource.php index 9963c9f8d6..5cb8dfc4cd 100644 --- a/lib/Analytics/AnalyticsDatasource.php +++ b/lib/Analytics/AnalyticsDatasource.php @@ -103,16 +103,9 @@ public function getTemplate(): array { // get all tables and subsequent views foreach ($tables as $table) { $tableString = $tableString . $table->getId() . '-' . $table->getTitle() . '/'; - // get all views per table - try { - $views = $this->viewService->findAll($this->tableService->find($table->getId()), $this->userId); - foreach ($views as $view) { - // concatenate the option-string. The format is tableId:viewId-title - $tableString = $tableString . $table->getId() . ':' . $view->getId() . '-' . $view->getTitle() . '/'; - } - } catch (PermissionError $e) { - // this is a shared table without shared views; - continue; + foreach ($table->getViews() ?? [] as $view) { + // concatenate the option-string. The format is tableId:viewId-title + $tableString = $tableString . $table->getId() . ':' . $view->getId() . '-' . $view->getTitle() . '/'; } } diff --git a/lib/Controller/Api1Controller.php b/lib/Controller/Api1Controller.php index 6e53050fd7..f8c305bcf2 100644 --- a/lib/Controller/Api1Controller.php +++ b/lib/Controller/Api1Controller.php @@ -326,7 +326,8 @@ public function deleteTable(int $tableId): DataResponse { #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] public function indexViews(int $tableId): DataResponse { try { - return new DataResponse($this->viewService->formatViews($this->viewService->findAll($this->tableService->find($tableId)))); + $table = $this->tableService->find($tableId); + return new DataResponse($this->viewService->formatViews($this->viewService->findAll($table, tableRowsCount: $table->getRowsCount()))); } catch (PermissionError $e) { $this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]); $message = ['message' => $e->getMessage()]; diff --git a/lib/Db/ColumnMapper.php b/lib/Db/ColumnMapper.php index 527b728d97..e3dd941381 100644 --- a/lib/Db/ColumnMapper.php +++ b/lib/Db/ColumnMapper.php @@ -165,24 +165,38 @@ public function getColumnTypes(array $neededColumnIds): array { } /** - * @param int $tableId - * @return int + * @param int[] $tableIds + * @return array */ - public function countColumns(int $tableId): int { - $qb = $this->db->getQueryBuilder(); - $qb->select($qb->func()->count('*', 'counter')); - $qb->from($this->table); - $qb->where( - $qb->expr()->eq('table_id', $qb->createNamedParameter($tableId)) - ); + public function countColumnsForTables(array $tableIds): array { + if (empty($tableIds)) { + return []; + } - try { - $result = $this->findOneQuery($qb); - return (int)$result['counter']; - } catch (DoesNotExistException|MultipleObjectsReturnedException|Exception $e) { - $this->logger->warning('Exception occurred: ' . $e->getMessage() . ' Returning 0.'); - return 0; + $counts = []; + foreach (array_chunk($tableIds, 1000 - 1) as $tableIdsChunk) { + $qb = $this->db->getQueryBuilder(); + $qb->select('table_id', $qb->func()->count('*', 'counter')) + ->from($this->table) + ->where($qb->expr()->in('table_id', $qb->createNamedParameter($tableIdsChunk, IQueryBuilder::PARAM_INT_ARRAY))) + ->groupBy('table_id'); + + try { + $result = $qb->executeQuery(); + while ($row = $result->fetch()) { + $counts[(int)$row['table_id']] = (int)$row['counter']; + } + $result->closeCursor(); + } catch (Exception $e) { + $this->logger->warning('Exception occurred: ' . $e->getMessage() . ' Returning 0 for all given tables.'); + } } + + foreach ($tableIds as $tableId) { + $counts[$tableId] ??= 0; + } + + return $counts; } /** diff --git a/lib/Db/Row2Mapper.php b/lib/Db/Row2Mapper.php index 7ec48500f9..27f8149795 100644 --- a/lib/Db/Row2Mapper.php +++ b/lib/Db/Row2Mapper.php @@ -962,8 +962,12 @@ public function deleteAllForTable(int $tableId, array $columns): void { } } - public function countRowsForTable(int $tableId): int { - return $this->rowSleeveMapper->countRows($tableId); + /** + * @param int[] $tableIds + * @return array + */ + public function countRowsForTables(array $tableIds): array { + return $this->rowSleeveMapper->countRowsForTables($tableIds); } /** diff --git a/lib/Db/RowSleeveMapper.php b/lib/Db/RowSleeveMapper.php index f5a21fe5c1..5ba2efeb29 100644 --- a/lib/Db/RowSleeveMapper.php +++ b/lib/Db/RowSleeveMapper.php @@ -103,24 +103,38 @@ public function deleteAllForTable(int $tableId): int { } /** - * @param int $tableId - * @return int + * @param int[] $tableIds + * @return array */ - public function countRows(int $tableId): int { - $qb = $this->db->getQueryBuilder(); - $qb->select($qb->func()->count('*', 'counter')); - $qb->from($this->table, 't1'); - $qb->where( - $qb->expr()->eq('table_id', $qb->createNamedParameter($tableId)) - ); - - try { - $result = $this->findOneQuery($qb); - return (int)$result['counter']; - } catch (DoesNotExistException|MultipleObjectsReturnedException|Exception $e) { - $this->logger->warning('Exception occurred: ' . $e->getMessage() . ' Will return 0.'); - return 0; + public function countRowsForTables(array $tableIds): array { + if (empty($tableIds)) { + return []; } + + $counts = []; + foreach (array_chunk($tableIds, 1000 - 1) as $tableIdsChunk) { + $qb = $this->db->getQueryBuilder(); + $qb->select('table_id', $qb->func()->count('*', 'counter')) + ->from($this->table, 't1') + ->where($qb->expr()->in('table_id', $qb->createNamedParameter($tableIdsChunk, IQueryBuilder::PARAM_INT_ARRAY))) + ->groupBy('table_id'); + + try { + $result = $qb->executeQuery(); + while ($row = $result->fetch()) { + $counts[(int)$row['table_id']] = (int)$row['counter']; + } + $result->closeCursor(); + } catch (Exception $e) { + $this->logger->warning('Exception occurred: ' . $e->getMessage() . ' Will return 0 for all given tables.'); + } + } + + foreach ($tableIds as $tableId) { + $counts[$tableId] ??= 0; + } + + return $counts; } /** diff --git a/lib/Db/ShareMapper.php b/lib/Db/ShareMapper.php index bb59ce7ceb..7feb7892d4 100644 --- a/lib/Db/ShareMapper.php +++ b/lib/Db/ShareMapper.php @@ -7,6 +7,7 @@ namespace OCA\Tables\Db; +use OCA\Tables\Constants\ShareReceiverType; use OCA\Tables\Service\ValueObject\ShareToken; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; @@ -97,7 +98,7 @@ public function findAllSharesFor(string $nodeType, array $receivers, string $use return []; } - $chunks = []; + $chunks = [[]]; // deduct extra parameters (sender, node type, receiver type) foreach (array_chunk($receivers, 1000 - 3) as $receiversChunk) { $qb = $this->db->getQueryBuilder(); @@ -136,6 +137,77 @@ public function findAllSharesForNode(string $nodeType, int $nodeId, string $send return $this->findEntities($qb); } + /** + * @param string $nodeType + * @param int[] $nodeIds + * @param string $sender + * @param array $excluded receiver types to exclude from results + * @return Share[] + * @throws Exception + */ + public function findAllSharesForNodes(string $nodeType, array $nodeIds, string $sender = '', array $excluded = []): array { + if (empty($nodeIds)) { + return []; + } + + $extraParams = 2; + if ($sender !== '') { + $extraParams++; + } + if (!empty($excluded)) { + $extraParams++; + } + + $chunks = [[]]; + foreach (array_chunk($nodeIds, 1000 - $extraParams) as $nodeIdsChunk) { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->table) + ->andWhere($qb->expr()->eq('node_type', $qb->createNamedParameter($nodeType, IQueryBuilder::PARAM_STR))) + ->andWhere($qb->expr()->in('node_id', $qb->createNamedParameter($nodeIdsChunk, IQueryBuilder::PARAM_INT_ARRAY))); + + if ($sender !== '') { + $qb->andWhere($qb->expr()->eq('sender', $qb->createNamedParameter($sender, IQueryBuilder::PARAM_STR))); + } + + if (!empty($excluded)) { + $qb->andWhere($qb->expr()->notIn('receiver_type', $qb->createNamedParameter($excluded, IQueryBuilder::PARAM_STR_ARRAY))); + } + + $chunks[] = $this->findEntities($qb); + } + + return array_merge(...$chunks); + } + + /** + * @param int[] $tableIds + * @return int[] + */ + public function findLinkSharesForTables(array $tableIds): array { + if (empty($tableIds)) { + return []; + } + + $linkTableIds = []; + foreach (array_chunk($tableIds, 1000 - 2) as $tableIdsChunk) { + $qb = $this->db->getQueryBuilder(); + $qb->selectDistinct('node_id') + ->from($this->table) + ->andWhere($qb->expr()->eq('node_type', $qb->createNamedParameter('table', IQueryBuilder::PARAM_STR))) + ->andWhere($qb->expr()->eq('receiver_type', $qb->createNamedParameter(ShareReceiverType::LINK, IQueryBuilder::PARAM_STR))) + ->andWhere($qb->expr()->in('node_id', $qb->createNamedParameter($tableIdsChunk, IQueryBuilder::PARAM_INT_ARRAY))); + + $result = $qb->executeQuery(); + while ($row = $result->fetch()) { + $linkTableIds[] = (int)$row['node_id']; + } + $result->closeCursor(); + } + + return $linkTableIds; + } + /** * @param string $nodeType * @param int $nodeId diff --git a/lib/Db/ViewMapper.php b/lib/Db/ViewMapper.php index 531803be5f..1dfeeedb4a 100644 --- a/lib/Db/ViewMapper.php +++ b/lib/Db/ViewMapper.php @@ -114,6 +114,25 @@ public function findAll(?int $tableId = null): array { return $this->findEntities($qb); } + /** + * @param int[] $tableIds + * @return View[] + * @throws Exception + */ + public function findAllByTableIds(array $tableIds): array { + if (empty($tableIds)) { + return []; + } + + $qb = $this->db->getQueryBuilder(); + $qb->select('v.*', 't.ownership') + ->from($this->table, 'v') + ->innerJoin('v', 'tables_tables', 't', 't.id = v.table_id') + ->where($qb->expr()->in('v.table_id', $qb->createNamedParameter($tableIds, IQueryBuilder::PARAM_INT_ARRAY))); + + return $this->findEntities($qb); + } + /** * @return View[] * @throws Exception diff --git a/lib/Helper/UserHelper.php b/lib/Helper/UserHelper.php index 1f4a37f5c8..b8c797017f 100644 --- a/lib/Helper/UserHelper.php +++ b/lib/Helper/UserHelper.php @@ -36,6 +36,23 @@ public function getUserDisplayName(string $userId): string { } } + /** + * @param string[] $userIds + * @return array + */ + public function getUsersDisplayNames(array $userIds): array { + $displayNames = []; + foreach (array_unique($userIds) as $userId) { + if ($userId === '' || $userId === null) { + $displayNames[$userId] = $userId; + continue; + } + $displayName = $this->userManager->getDisplayName($userId); + $displayNames[$userId] = $displayName ?: $userId; + } + return $displayNames; + } + /** * @throws InternalError */ diff --git a/lib/Service/ColumnService.php b/lib/Service/ColumnService.php index 341c1f13c3..99cd5d8578 100644 --- a/lib/Service/ColumnService.php +++ b/lib/Service/ColumnService.php @@ -692,16 +692,19 @@ public function findOrCreateColumnsByTitleForTableAsArray(?int $tableId, ?int $v } /** - * @param int $tableId - * @return int - * @throws PermissionError + * @param int[] $tableIds + * @param string|null $userId + * @return array */ - public function getColumnsCount(int $tableId): int { - if ($this->permissionsService->canReadColumnsByTableId($tableId)) { - return $this->mapper->countColumns($tableId); - } else { - throw new PermissionError('no read access for counting to table id = ' . $tableId); + public function getColumnsCountForTables(array $tableIds, ?string $userId = null): array { + $userId = $this->permissionsService->preCheckUserId($userId); + $counts = $this->mapper->countColumnsForTables($tableIds); + foreach ($tableIds as $tableId) { + if (!$this->permissionsService->canReadColumnsByTableId($tableId, $userId)) { + $counts[$tableId] = 0; + } } + return $counts; } /** diff --git a/lib/Service/RowService.php b/lib/Service/RowService.php index 252e428118..3187cb324b 100644 --- a/lib/Service/RowService.php +++ b/lib/Service/RowService.php @@ -861,17 +861,19 @@ public function deleteColumnDataFromRows(Column $column):void { } /** - * @param int $tableId - * @return int - * - * @throws PermissionError + * @param int[] $tableIds + * @param string|null $userId + * @return array */ - public function getRowsCount(int $tableId): int { - if ($this->permissionsService->canReadRowsByElementId($tableId, 'table')) { - return $this->row2Mapper->countRowsForTable($tableId); - } else { - throw new PermissionError('no read access for counting to table id = ' . $tableId); + public function getRowsCountForTables(array $tableIds, ?string $userId = null): array { + $userId = $this->permissionsService->preCheckUserId($userId); + $counts = $this->row2Mapper->countRowsForTables($tableIds); + foreach ($tableIds as $tableId) { + if (!$this->permissionsService->canReadRowsByElementId($tableId, 'table', $userId)) { + $counts[$tableId] = 0; + } } + return $counts; } /** diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index 2438e1c6f7..94442ebb26 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -248,6 +248,80 @@ public function findTablesSharedWithMe(?string $userId = null): array { return $this->findElementsSharedWithMe('table', $userId); } + /** + * @param int[] $tableIds + * @param string|null $userId + * @return array + * @throws InternalError + */ + public function countSharesForTables(array $tableIds, ?string $userId = null): array { + $userId = $this->permissionsService->preCheckUserId($userId); + + try { + $excluded = !$this->circleHelper->isCirclesEnabled() ? [ShareReceiverType::CIRCLE] : []; + $shares = $this->mapper->findAllSharesForNodes('table', $tableIds, $userId, $excluded); + + $counts = []; + foreach ($shares as $share) { + $nodeId = $share->getNodeId(); + $counts[$nodeId] = ($counts[$nodeId] ?? 0) + 1; + } + + foreach ($tableIds as $tableId) { + $counts[$tableId] ??= 0; + } + + return $counts; + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + throw new InternalError($e->getMessage()); + } + } + + /** + * @param int[] $viewIds + * @param string|null $userId + * @return array + * @throws InternalError + */ + public function countSharesForViews(array $viewIds, ?string $userId = null): array { + $userId = $this->permissionsService->preCheckUserId($userId); + + try { + $excluded = !$this->circleHelper->isCirclesEnabled() ? [ShareReceiverType::CIRCLE] : []; + $shares = $this->mapper->findAllSharesForNodes('view', $viewIds, $userId, $excluded); + + $counts = []; + foreach ($shares as $share) { + $nodeId = $share->getNodeId(); + $counts[$nodeId] = ($counts[$nodeId] ?? 0) + 1; + } + + foreach ($viewIds as $viewId) { + $counts[$viewId] ??= 0; + } + + return $counts; + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + throw new InternalError($e->getMessage()); + } + } + + /** + * @param int[] $tableIds + * @return int[] + * @throws InternalError + */ + public function getTableIdsWithLinkShares(array $tableIds): array { + try { + return $this->mapper->findLinkSharesForTables($tableIds); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + throw new InternalError($e->getMessage()); + } + } + /** * @throws InternalError */ diff --git a/lib/Service/TableService.php b/lib/Service/TableService.php index c06f89ccfd..059b538d97 100644 --- a/lib/Service/TableService.php +++ b/lib/Service/TableService.php @@ -127,24 +127,17 @@ public function findAll(?string $userId = null, bool $skipTableEnhancement = fal ) { continue; } - $allTables[$node['node_id']] = $this->find($node['node_id'], $skipTableEnhancement, $userId); + $allTables[$node['node_id']] = $this->find($node['node_id'], true, $userId); } } // enhance table objects with additional data if (!$skipTableEnhancement) { - foreach ($allTables as $table) { - /** @var string $userId */ - try { - $this->enhanceTable($table, $userId); - } catch (InternalError|PermissionError $e) { - $this->logger->error($e->getMessage(), ['exception' => $e]); - } - // if the table is shared with me, there are no other shares - // will avoid showing the shared icon in the FE nav - if ($table->getIsShared()) { - $table->setHasShares(false); - } + /** @var string $userId */ + try { + $this->enhanceTables($allTables, $userId); + } catch (InternalError|PermissionError $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); } } @@ -160,51 +153,107 @@ public function formatTables(array $tables): array { } /** - * add some basic values related to this table in context + * Adds some basic values related to a list of tables in context (rows count, + * columns count, shares count, etc.) * * $userId can be set or '' - * @param Table $table + * + * @param Table[] $tables * @param string $userId * @throws InternalError * @throws PermissionError */ - private function enhanceTable(Table $table, string $userId): void { - // add owner display name for UI - $this->addOwnerDisplayName($table); + private function enhanceTables(array $tables, string $userId): void { + if (empty($tables)) { + return; + } + + $tablesById = []; + foreach ($tables as $table) { + $tablesById[$table->getId()] = $table; + } + $tableIds = array_keys($tablesById); + + // add owner display names for UI + $ownerIds = array_values(array_filter( + array_unique(array_map(static fn (Table $table) => $table->getOwnership(), $tables)) + )); + $ownerDisplayNames = $this->userHelper->getUsersDisplayNames($ownerIds); + foreach ($tables as $table) { + $ownerId = $table->getOwnership() ?? ''; + $table->setOwnerDisplayName($ownerDisplayNames[$ownerId] ?? $ownerId); + } + + // add the rows and columns counts + $rowsCounts = $this->rowService->getRowsCountForTables($tableIds, $userId); + $columnsCounts = $this->columnService->getColumnsCountForTables($tableIds, $userId); + foreach ($tables as $table) { + $table->setRowsCount($rowsCounts[$table->getId()] ?? 0); + $table->setColumnsCount($columnsCounts[$table->getId()] ?? 0); + } + + // set hasShares / public isShared in one batch + if ($userId === '') { + $linkShareTableIds = $this->shareService->getTableIdsWithLinkShares($tableIds); + $linkShareTableIds = array_flip($linkShareTableIds); + foreach ($tables as $table) { + $table->setIsShared(isset($linkShareTableIds[$table->getId()])); + $table->setOnSharePermissions(new Permissions(read: true)); + } + } else { + $ownedTableIds = array_filter($tableIds, static fn (int $id) => $tablesById[$id]->getOwnership() === $userId); + $sharesCount = $this->shareService->countSharesForTables($ownedTableIds, $userId); + foreach ($tables as $table) { + $table->setHasShares(($sharesCount[$table->getId()] ?? 0) > 0); + } + } - // set hasShares if this table is shared by you (you share it with somebody else) - // (senseless if we have no user in context) + // set isShared and onSharePermissions (kept as is, per table) if ($userId !== '') { - try { - $shares = $this->shareService->findAll('table', $table->getId()); - $table->setHasShares(count($shares) !== 0); - } catch (InternalError $e) { + foreach ($tables as $table) { + try { + $this->setIsSharedState($table, $userId); + } catch (InternalError|PermissionError $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); + $table->setIsShared(false); + $table->setOnSharePermissions(new Permissions()); + } } } - // add the rows count - try { - $table->setRowsCount($this->rowService->getRowsCount($table->getId())); - } catch (PermissionError $e) { - $table->setRowsCount(0); + // if the table is shared with me, there are no other shares + // will avoid showing the shared icon in the FE nav + foreach ($tables as $table) { + if ($table->getIsShared()) { + $table->setHasShares(false); + } } - // add the column count - try { - $table->setColumnsCount($this->columnService->getColumnsCount($table->getId())); - } catch (PermissionError $e) { - $table->setColumnsCount(0); + // add the corresponding views if it is an own table, or you have table manage rights + $tablesToLoadViews = []; + foreach ($tables as $table) { + if (!$table->getIsShared() || $table->getOnSharePermissions()->manage) { + $tablesToLoadViews[] = $table; + } } - $this->setIsSharedState($table, $userId); - - if (!$table->getIsShared() || $table->getOnSharePermissions()->manage) { - // add the corresponding views if it is an own table, or you have table manage rights - $table->setViews($this->viewService->findAll($table)); + try { + $viewsByTable = $this->viewService->findForTables($tablesToLoadViews, $userId, $rowsCounts); + foreach ($tablesToLoadViews as $table) { + $table->setViews($viewsByTable[$table->getId()] ?? []); + } + } catch (InternalError|PermissionError $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); + foreach ($tablesToLoadViews as $table) { + $table->setViews([]); + } } - if ($this->favoritesService->isFavorite(Application::NODE_TYPE_TABLE, $table->getId())) { - $table->setFavorite(true); + // add favorites + foreach ($tables as $table) { + if ($this->favoritesService->isFavorite(Application::NODE_TYPE_TABLE, $table->getId())) { + $table->setFavorite(true); + } } } @@ -251,7 +300,7 @@ public function find(int $id, bool $skipTableEnhancement = false, ?string $userI } if (!$skipTableEnhancement) { - $this->enhanceTable($table, $userId); + $this->enhanceTables([$table], $userId); } return $table; @@ -304,11 +353,11 @@ public function create(string $title, string $template, ?string $emoji, ?string throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); } } else { - $table = $this->addOwnerDisplayName($newTable); + $table = $newTable; } try { - $this->enhanceTable($table, $userId); + $this->enhanceTables([$table], $userId); } catch (InternalError|PermissionError $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); @@ -526,7 +575,7 @@ public function update(int $id, ?string $title, ?string $emoji, ?string $descrip throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); } try { - $this->enhanceTable($table, $userId); + $this->enhanceTables([$table], $userId); } catch (InternalError|PermissionError $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); @@ -552,9 +601,7 @@ public function search(string $term, int $limit = 100, int $offset = 0, ?string /** @var string $userId */ $userId = $this->permissionsService->preCheckUserId($userId); $tables = $this->mapper->search($term, $userId, $limit, $offset); - foreach ($tables as &$table) { - $this->enhanceTable($table, $userId); - } + $this->enhanceTables($tables, $userId); return $tables; } catch (InternalError|PermissionError|OcpDbException $e) { return []; @@ -569,21 +616,12 @@ public function search(string $term, int $limit = 100, int $offset = 0, ?string public function getScheme(int $id, ?string $userId = null): TableScheme { $table = $this->find($id, skipTableEnhancement: true); $columns = $this->columnService->findAllByTable($id, null, $table); - $this->enhanceTable($table, $userId); + $this->enhanceTables([$table], $userId); return new TableScheme($table->getTitle(), $table->getEmoji(), $columns, $table->getViews() ?: [], $table->getDescription() ?: '', $this->appManager->getAppVersion('tables'), $table->getColumnOrderSettingsArray(), $table->getSortArray()); } // PRIVATE FUNCTIONS --------------------------------------------------------------- - /** - * @param Table $table - * @return Table - */ - private function addOwnerDisplayName(Table $table): Table { - $table->setOwnerDisplayName($this->userHelper->getUserDisplayName($table->getOwnership())); - return $table; - } - /** * @param array $table * @param string $userId diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index a028e2fb65..c099899ce5 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -90,31 +90,55 @@ public function __construct( /** * @param Table $table * @param string|null $userId + * @param int|null $tableRowsCount * @return array * @throws InternalError * @throws PermissionError */ - public function findAll(Table $table, ?string $userId = null): array { + public function findAll(Table $table, ?string $userId = null, ?int $tableRowsCount = null): array { + /** @var string $userId */ $userId = $this->permissionsService->preCheckUserId($userId); // $userId can be set or '' - try { - // security - if (!$this->permissionsService->canManageTable($table, $userId)) { - throw new PermissionError('PermissionError: can not read views for tableId ' . $table->getId()); - } + // security + if (!$this->permissionsService->canManageTable($table, $userId)) { + throw new PermissionError('PermissionError: can not read views for tableId ' . $table->getId()); + } - $allViews = $this->mapper->findAll($table->getId()); - foreach ($allViews as $view) { - $this->enhanceView($view, $userId); - } - return $allViews; + $viewsByTable = $this->findForTables([$table], $userId, [$table->getId() => $tableRowsCount]); + return $viewsByTable[$table->getId()] ?? []; + } + + /** + * @param Table[] $tables + * @param string|null $userId + * @param array $tableRowsCounts + * @return array + * @throws InternalError + */ + public function findForTables(array $tables, ?string $userId = null, array $tableRowsCounts = []): array { + /** @var string $userId */ + $userId = $this->permissionsService->preCheckUserId($userId); // $userId can be set or '' + + if (empty($tables)) { + return []; + } + + $tableIds = array_map(static fn (Table $table) => $table->getId(), $tables); + + try { + $allViews = $this->mapper->findAllByTableIds($tableIds); } catch (\OCP\DB\Exception|InternalError $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new InternalError($e->getMessage()); - } catch (PermissionError $e) { - $this->logger->debug('permission error during looking for views', ['exception' => $e]); - throw new PermissionError($e->getMessage()); } + + $this->enhanceViews($allViews, $userId, $tableRowsCounts); + + $viewsByTable = []; + foreach ($allViews as $view) { + $viewsByTable[$view->getTableId()][] = $view; + } + return $viewsByTable; } /** @@ -185,9 +209,7 @@ public function findSharedViewsWithMe(?string $userId = null): array { } } - foreach ($sharedViews as $view) { - $this->enhanceView($view, $userId); - } + $this->enhanceViews($sharedViews, $userId, []); return array_values($sharedViews); } @@ -424,54 +446,114 @@ public function deleteByObject(View $view, ?string $userId = null): View { * add some basic values related to this view in context * * $userId can be set or '' + * + * @param int|null $tableRowsCount */ - private function enhanceView(View $view, string $userId): void { - // add owner display name for UI - $view->setOwnerDisplayName($this->userHelper->getUserDisplayName($view->getOwnership())); - - $this->setIsSharedState($view, $userId); + private function enhanceView(View $view, string $userId, ?int $tableRowsCount = null): void { + $this->enhanceViews([$view], $userId, [$view->getTableId() => $tableRowsCount]); + } - if (!$this->permissionsService->canReadRowsByElement($view, 'view', $userId)) { + /** + * add some basic values related to a list of views in context + * + * $userId can be set or '' + * + * @param View[] $views + * @param array $tableRowsCounts + */ + private function enhanceViews(array $views, string $userId, array $tableRowsCounts = []): void { + if (empty($views)) { return; } - // add the rows count - try { - $view->setRowsCount($this->rowService->getViewRowsCount($view, $userId)); - } catch (InternalError|PermissionError $e) { - } - - // Remove detailed view filtering and sorting information if necessary - if ($view->getIsShared() && !$view->getOnSharePermissions()->manageTable) { - $view->setFilterArray([]); - - $rawSortArray = $view->getSortArray(); - if ($rawSortArray) { - $view->setSortArray( - array_map( - static function (array $sortRule) use ($view): array { - if (isset($sortRule['columnId']) - && ( - Column::isValidMetaTypeId($sortRule['columnId']) - || in_array($sortRule['columnId'], $view->getColumnIds(), true) - ) - ) { - return $sortRule; - } - // Instead of sort rule just indicate that there is a rule, but hide details - return []; - }, - $rawSortArray - ) - ); + + // add owner display names for UI in one batch + $ownerIds = array_values(array_filter( + array_unique(array_map(static fn (View $view) => $view->getOwnership(), $views)) + )); + $ownerDisplayNames = $this->userHelper->getUsersDisplayNames($ownerIds); + foreach ($views as $view) { + $ownerId = $view->getOwnership() ?? ''; + $view->setOwnerDisplayName($ownerDisplayNames[$ownerId] ?? $ownerId); + } + + $rowsCountCache = []; + + $sharesCounts = []; + if ($userId !== '') { + $ownedViewIds = []; + foreach ($views as $view) { + if ($userId === $view->getOwnership()) { + $ownedViewIds[] = $view->getId(); + } + } + + if (!empty($ownedViewIds)) { + try { + $sharesCounts = $this->shareService->countSharesForViews($ownedViewIds, $userId); + } catch (InternalError $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); + } } } - if ($this->favoritesService->isFavorite(Application::NODE_TYPE_VIEW, $view->getId())) { - $view->setFavorite(true); + foreach ($views as $view) { + $this->setIsSharedState($view, $userId, $sharesCounts[$view->getId()] ?? null); + + if (!$this->permissionsService->canReadRowsByElement($view, 'view', $userId)) { + continue; + } + + // add the rows count + $tableRowsCount = $tableRowsCounts[$view->getTableId()] ?? null; + if ($tableRowsCount !== null && $view->getFilterArray() === []) { + $view->setRowsCount($tableRowsCount); + } else { + $cacheKey = $view->getTableId() . ':' . json_encode($view->getFilterArray()) . ':' . $userId; + if (isset($rowsCountCache[$cacheKey])) { + $view->setRowsCount($rowsCountCache[$cacheKey]); + } else { + try { + $count = $this->rowService->getViewRowsCount($view, $userId); + $view->setRowsCount($count); + $rowsCountCache[$cacheKey] = $count; + } catch (InternalError|PermissionError $e) { + } + } + } + + // Remove detailed view filtering and sorting information if necessary + if ($view->getIsShared() && !$view->getOnSharePermissions()->manageTable) { + $view->setFilterArray([]); + + $rawSortArray = $view->getSortArray(); + if ($rawSortArray) { + $view->setSortArray( + array_map( + static function (array $sortRule) use ($view): array { + if (isset($sortRule['columnId']) + && ( + Column::isValidMetaTypeId($sortRule['columnId']) + || in_array($sortRule['columnId'], $view->getColumnIds(), true) + ) + ) { + return $sortRule; + } + // Instead of sort rule just indicate that there is a rule, but hide details + return []; + }, + $rawSortArray + ) + ); + } + } + + if ($this->favoritesService->isFavorite(Application::NODE_TYPE_VIEW, $view->getId())) { + $view->setFavorite(true); + } } } - private function setIsSharedState(View $view, string $userId): void { + private function setIsSharedState(View $view, string $userId, ?int $sharesCount = null): void { // set if this is a shared table with you (somebody else shared it with you) // (senseless if we have no user in context) if ($userId !== '') { @@ -511,10 +593,14 @@ private function setIsSharedState(View $view, string $userId): void { } else { // set hasShares if this table is shared by you (you share it with somebody else) // (senseless if we have no user in context) - try { - $allShares = $this->shareService->findAll('view', $view->getId()); - $view->setHasShares(count($allShares) !== 0); - } catch (InternalError $e) { + if ($sharesCount !== null) { + $view->setHasShares($sharesCount > 0); + } else { + try { + $allShares = $this->shareService->findAll('view', $view->getId(), $userId); + $view->setHasShares(count($allShares) !== 0); + } catch (InternalError $e) { + } } } } else { @@ -609,9 +695,7 @@ public function search(string $term, int $limit = 100, int $offset = 0, ?string /** @var string $userId */ $userId = $this->permissionsService->preCheckUserId($userId); $views = $this->mapper->search($term, $userId, $limit, $offset); - foreach ($views as $view) { - $this->enhanceView($view, $userId); - } + $this->enhanceViews($views, $userId, []); return $views; } catch (InternalError|\OCP\DB\Exception $e) { return [];