From 8280f3118075fcd06e573f1a10a720f0d4f78ec9 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 15 Jan 2024 11:44:37 +0200 Subject: [PATCH] global stream optimization --- .../Espo/Tools/Stream/GlobalRecordService.php | 168 ++++++++++++++---- .../Stream/RecordService/QueryHelper.php | 2 +- 2 files changed, 139 insertions(+), 31 deletions(-) diff --git a/application/Espo/Tools/Stream/GlobalRecordService.php b/application/Espo/Tools/Stream/GlobalRecordService.php index 4de3f8752c..e0d3747656 100644 --- a/application/Espo/Tools/Stream/GlobalRecordService.php +++ b/application/Espo/Tools/Stream/GlobalRecordService.php @@ -40,6 +40,9 @@ use Espo\Entities\User; use Espo\ORM\Collection; use Espo\ORM\EntityManager; use Espo\ORM\Query\Part\Order; +use Espo\ORM\Query\Select; +use Espo\ORM\Query\SelectBuilder; +use Espo\ORM\SthCollection; use Espo\Tools\Stream\RecordService\QueryHelper; class GlobalRecordService @@ -66,23 +69,13 @@ class GlobalRecordService $this->preCheck($searchParams); $maxSize = $searchParams->getMaxSize() ?? 0; + $entityTypeList = $this->getEntityTypeList(); $baseBuilder = $this->queryHelper->buildBaseQueryBuilder($searchParams) ->select($this->queryHelper->getUserQuerySelect()) ->order('number', Order::DESC) - ->where([ - 'OR' => [ - ['parentType' => $this->getEntityTypeList()], - [ - 'parentType' => null, - 'type' => Note::TYPE_POST, - ], - ] - ]) ->limit(0, $maxSize + 1); - $builder = (clone $baseBuilder); - /** @var array{string, string}[] $ignoreList */ $ignoreList = []; /** @var array{string, string}[] $allowList */ @@ -90,26 +83,19 @@ class GlobalRecordService $list = []; $i = 0; + $iterationBuilder = (clone $baseBuilder); while (true) { - $ignoreWhere = []; + $queryList = []; - foreach ($ignoreList as $it) { - $ignoreWhere[] = [ - 'OR' => [ - 'parentId' => null, - 'parentType!=' => $it[0], - 'parentId!=' => $it[1] - ] - ]; - } + $this->buildBelongToParentQuery($iterationBuilder, $queryList, $entityTypeList, $ignoreList); + $this->buildPostedToUserQuery($iterationBuilder, $queryList); + $this->buildPostedToPortalQuery($iterationBuilder, $queryList); + $this->buildPostedToTeamsQuery($iterationBuilder, $queryList); + $this->buildPostedByUserQuery($iterationBuilder, $queryList); + $this->buildPostedToGlobalQuery($iterationBuilder, $queryList); - $collection = $this->entityManager - ->getRDBRepositoryByClass(Note::class) - ->clone($builder->build()) - ->sth() - ->where($ignoreWhere) - ->find(); + $collection = $this->fetchCollection($queryList, $maxSize); /** @var Note[] $subList */ $subList = iterator_to_array($collection); @@ -118,9 +104,13 @@ class GlobalRecordService break; } + // Should be obtained before filtering. $lastNumber = end($subList)->getNumber(); - $list = array_merge($list, $this->filter($subList, $ignoreList, $allowList)); + $list = array_merge( + $list, + $this->filter($subList, $ignoreList, $allowList), + ); if (count($list) >= $maxSize + 1) { break; @@ -133,7 +123,7 @@ class GlobalRecordService break; } - $builder = (clone $baseBuilder)->where(['number<' => $lastNumber]); + $iterationBuilder = (clone $baseBuilder)->where(['number<' => $lastNumber]); } $list = array_slice($list, 0, $maxSize + 1); @@ -219,7 +209,8 @@ class GlobalRecordService $parentId = $note->getParentId(); if (!$note->getParentType()) { - return $this->acl->checkEntityRead($note); + // Only proper records are fetched. + return true; } if (!$parentType || !$parentId) { @@ -284,4 +275,121 @@ class GlobalRecordService throw new BadRequest("Offset is not supported."); } } + + /** + * @param Select[] $queryList + * @param int $maxSize + * @return SthCollection + */ + private function fetchCollection(array $queryList, int $maxSize): SthCollection + { + $unionBuilder = $this->entityManager + ->getQueryBuilder() + ->union() + ->all() + ->order('number', Order::DESC) + ->limit(0, $maxSize + 1); + + foreach ($queryList as $query) { + $unionBuilder->query($query); + } + + $unionQuery = $unionBuilder->build(); + + $sql = $this->entityManager + ->getQueryComposer() + ->compose($unionQuery); + + /** @var SthCollection */ + return $this->entityManager + ->getRDBRepositoryByClass(Note::class) + ->findBySql($sql); + } + + /** + * @param Select[] $queryList + */ + private function buildPostedToUserQuery(SelectBuilder $baseBuilder, array &$queryList): void + { + $queryList[] = $this->queryHelper->buildPostedToUserQuery($this->user, $baseBuilder); + } + + /** + * @param Select[] $queryList + */ + private function buildPostedToPortalQuery(SelectBuilder $baseBuilder, array &$queryList): void + { + $query = $this->queryHelper->buildPostedToPortalQuery($this->user, $baseBuilder); + + if (!$query) { + return; + } + + $queryList[] = $query; + } + + /** + * @param Select[] $queryList + */ + private function buildPostedToTeamsQuery(SelectBuilder $baseBuilder, array &$queryList): void + { + $query = $this->queryHelper->buildPostedToTeamsQuery($this->user, $baseBuilder); + + if (!$query) { + return; + } + + $queryList[] = $query; + } + + /** + * @param Select[] $queryList + */ + private function buildPostedByUserQuery(SelectBuilder $baseBuilder, array &$queryList): void + { + $queryList[] = $this->queryHelper->buildPostedByUserQuery($this->user, $baseBuilder); + } + + /** + * @param Select[] $queryList + */ + private function buildPostedToGlobalQuery(SelectBuilder $baseBuilder, array &$queryList): void + { + $query = $this->queryHelper->buildPostedToGlobalQuery($this->user, $baseBuilder); + + if (!$query) { + return; + } + + $queryList[] = $query; + } + + /** + * @param Select[] $queryList + * @param string[] $entityTypeList + * @param array{string, string}[] $ignoreList + */ + private function buildBelongToParentQuery( + SelectBuilder $builder, + array &$queryList, + array $entityTypeList, + array $ignoreList + ): void { + + $ignoreWhere = []; + + foreach ($ignoreList as $it) { + $ignoreWhere[] = [ + 'OR' => [ + 'parentType!=' => $it[0], + 'parentId!=' => $it[1] + ] + ]; + } + + $queryList[] = (clone $builder) + ->where(['parentType' => $entityTypeList]) + ->where($ignoreWhere) + ->build(); + } } diff --git a/application/Espo/Tools/Stream/RecordService/QueryHelper.php b/application/Espo/Tools/Stream/RecordService/QueryHelper.php index 0c7d64aa47..0f29d94d58 100644 --- a/application/Espo/Tools/Stream/RecordService/QueryHelper.php +++ b/application/Espo/Tools/Stream/RecordService/QueryHelper.php @@ -227,7 +227,7 @@ class QueryHelper 'type' => Note::TYPE_POST, 'targetType' => Note::TARGET_ALL, 'parentId' => null, - 'createdBy!=' => $user->getId(), + 'createdById!=' => $user->getId(), 'isGlobal' => true, ]) ->build();