Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion eZ/Publish/API/Repository/ContentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,8 @@ public function find(Filter $filter, ?array $languages = null): ContentList;
* @param string[] $languages a list of language codes to be added as additional constraints.
* If skipped, by default, unless SiteAccessAware layer has been disabled, languages set
* for a SiteAccess in a current context will be used.
* @param int|null $limit If set, the count will be limited to first $limit items found.
* In some cases it can significantly speed up a count operation for more complex filters.
*/
public function count(Filter $filter, ?array $languages = null): int;
public function count(Filter $filter, ?array $languages = null, ?int $limit = null): int;
}
11 changes: 8 additions & 3 deletions eZ/Publish/API/Repository/LocationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,17 @@ public function loadParentLocationsForDraftContent(VersionInfo $versionInfo, ?ar
*
* @return int
*/
public function getLocationChildCount(Location $location): int;
public function getLocationChildCount(Location $location, ?int $limit = null): int;

/**
* Return the subtree size of a given location.
*
* Warning! This method is not permission aware by design.
*
* @param \eZ\Publish\API\Repository\Values\Content\Location $location
* @param int|null $limit Optional limit to the number of locations to count. (Can be used to limit the number of locations counted in large subtrees.)
*/
public function getSubtreeSize(Location $location): int;
public function getSubtreeSize(Location $location, ?int $limit = null): int;

/**
* Creates the new $location in the content repository for the given content.
Expand Down Expand Up @@ -275,6 +278,8 @@ public function find(Filter $filter, ?array $languages = null): LocationList;
* @param string[] $languages a list of language codes to be added as additional constraints.
* If skipped, by default, unless SiteAccessAware layer has been disabled, languages set
* for a SiteAccess in a current context will be used.
* @param int|null $limit If set, the count will be limited to first $limit items found.
* In some cases it can significantly speed up a count operation for more complex filters.
*/
public function count(Filter $filter, ?array $languages = null): int;
public function count(Filter $filter, ?array $languages = null, ?int $limit = null): int;
}
56 changes: 56 additions & 0 deletions eZ/Publish/API/Repository/Tests/LocationServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,26 @@ public function testGetLocationChildCount()
);
}

/**
* Test for the getLocationChildCount() method with a limitation on the number of children.
*
* @see \eZ\Publish\API\Repository\LocationService::getLocationChildCount()
* @depends eZ\Publish\API\Repository\Tests\LocationServiceTest::testLoadLocation
*/
public function testGetLocationChildCountWithLimitation()
{
// $locationId is the ID of an existing location
$locationService = $this->getRepository()->getLocationService();

$this->assertSame(
2,
$locationService->getLocationChildCount(
$locationService->loadLocation($this->generateId('location', 5)),
2
)
);
}

/**
* Test for the loadLocationChildren() method.
*
Expand Down Expand Up @@ -3552,6 +3572,42 @@ public function testGetSubtreeSize(): Location
return $location;
}

public function testGetSubtreeSizeWithLimit(): Location
{
$repository = $this->getRepository();
$locationService = $repository->getLocationService();

$folder = $this->createFolder(['eng-GB' => 'Parent Folder'], 2);
$location = $folder->getVersionInfo()->getContentInfo()->getMainLocation();
self::assertSame(1, $locationService->getSubtreeSize($location));

for ($i = 1; $i <= 10; ++$i) {
$this->createFolder(['eng-GB' => 'Child ' . $i], $location->id);
}

self::assertSame(3, $locationService->getSubtreeSize($location, 3));

return $location;
}

public function testGetSubtreeSizeWithInvalidLimitHasNoEffect(): Location
{
$repository = $this->getRepository();
$locationService = $repository->getLocationService();

$folder = $this->createFolder(['eng-GB' => 'Parent Folder'], 2);
$location = $folder->getVersionInfo()->getContentInfo()->getMainLocation();
self::assertSame(1, $locationService->getSubtreeSize($location));

for ($i = 1; $i <= 10; ++$i) {
$this->createFolder(['eng-GB' => 'Child ' . $i], $location->id);
}

self::assertSame(11, $locationService->getSubtreeSize($location, -2));

return $location;
}

/**
* Loads properties from all locations in the $location's subtree.
*
Expand Down
4 changes: 2 additions & 2 deletions eZ/Publish/Core/Persistence/Cache/LocationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,13 @@ public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
return $this->persistenceHandler->locationHandler()->copySubtree($sourceId, $destinationParentId, $newOwnerId);
}

public function getSubtreeSize(string $path): int
public function getSubtreeSize(string $path, ?int $limit = null): int
{
$this->logger->logCall(__METHOD__, [
'path' => $path,
]);

return $this->persistenceHandler->locationHandler()->getSubtreeSize($path);
return $this->persistenceHandler->locationHandler()->getSubtreeSize($path, $limit);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ abstract public function loadParentLocationsDataForDraftContent(int $contentId):
*/
abstract public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array;

abstract public function getSubtreeSize(string $path): int;
abstract public function getSubtreeSize(string $path, ?int $limit = null): int;

/**
* Returns data for the first level children of the location identified by given $locationId.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway as ContentGateway;
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator;
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway;
use eZ\Publish\Core\Persistence\Legacy\Traits\Doctrine\LimitedCountQueryTrait;
use eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\CriteriaConverter;
use eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\SortClauseConverter;
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
Expand All @@ -35,6 +36,8 @@
*/
final class DoctrineDatabase extends Gateway
{
use LimitedCountQueryTrait;

/** @var \Doctrine\DBAL\Connection */
private $connection;

Expand Down Expand Up @@ -237,18 +240,24 @@ public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array
: $results;
}

public function getSubtreeSize(string $path): int
public function getSubtreeSize(string $path, ?int $limit = null): int
{
$query = $this->createNodeQueryBuilder([$this->dbPlatform->getCountExpression('node_id')]);
$query->andWhere(
$query->expr()->like(
't.path_string',
$query->createPositionalParameter(
$path . '%',
)
),
)
);

$query = $this->wrapCountQuery(
$query,
't.node_id',
$limit
);

return (int) $query->execute()->fetchOne();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array
}
}

public function getSubtreeSize(string $path): int
public function getSubtreeSize(string $path, ?int $limit = null): int
{
try {
return $this->innerGateway->getSubtreeSize($path);
return $this->innerGateway->getSubtreeSize($path, $limit);
} catch (DBALException | PDOException $e) {
throw DatabaseException::wrap($e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
return $copiedSubtreeRootLocation;
}

public function getSubtreeSize(string $path): int
public function getSubtreeSize(string $path, ?int $limit = null): int
{
return $this->locationGateway->getSubtreeSize($path);
return $this->locationGateway->getSubtreeSize($path, $limit);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway as ContentGateway;
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
use eZ\Publish\Core\Persistence\Legacy\Filter\Gateway\Gateway;
use eZ\Publish\Core\Persistence\Legacy\Traits\Doctrine\LimitedCountQueryTrait;
use eZ\Publish\SPI\Persistence\Filter\CriterionVisitor;
use eZ\Publish\SPI\Persistence\Filter\Doctrine\FilteringQueryBuilder;
use eZ\Publish\SPI\Persistence\Filter\SortClauseVisitor;
Expand All @@ -31,6 +32,8 @@
*/
final class DoctrineGateway implements Gateway
{
use LimitedCountQueryTrait;

public const COLUMN_MAP = [
// Content Info
'content_id' => 'content.id',
Expand Down Expand Up @@ -87,13 +90,19 @@ private function getDatabasePlatform(): AbstractPlatform
}
}

public function count(FilteringCriterion $criterion): int
public function count(FilteringCriterion $criterion, ?int $limit = null): int
{
$query = $this->buildQuery(
[$this->getDatabasePlatform()->getCountExpression('DISTINCT content.id')],
$criterion
);

$query = $this->wrapCountQuery(
$query,
'content.id',
$limit
);

return (int)$query->execute()->fetch(FetchMode::COLUMN);
}

Expand Down
4 changes: 2 additions & 2 deletions eZ/Publish/Core/Persistence/Legacy/Filter/Gateway/Gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
interface Gateway
{
/**
* Return number of matched rows for the given Criteria (a total count w/o pagination constraints).
* Return number of matched rows for the given Criteria (a total count w/o pagination constraints unless an upper limit is applied).
*/
public function count(FilteringCriterion $criterion): int;
public function count(FilteringCriterion $criterion, ?int $limit = null): int;

/**
* Return iterator for raw Repository data for the given Query result filtered by the given Criteria,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use eZ\Publish\Core\Persistence\Legacy\Content\Gateway as ContentGateway;
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
use eZ\Publish\Core\Persistence\Legacy\Filter\Gateway\Gateway;
use eZ\Publish\Core\Persistence\Legacy\Traits\Doctrine\LimitedCountQueryTrait;
use eZ\Publish\SPI\Persistence\Filter\CriterionVisitor;
use eZ\Publish\SPI\Persistence\Filter\Doctrine\FilteringQueryBuilder;
use eZ\Publish\SPI\Persistence\Filter\SortClauseVisitor;
Expand All @@ -26,6 +27,8 @@
*/
final class DoctrineGateway implements Gateway
{
use LimitedCountQueryTrait;

/** @var \Doctrine\DBAL\Connection */
private $connection;

Expand Down Expand Up @@ -54,12 +57,18 @@ private function getDatabasePlatform(): AbstractPlatform
}
}

public function count(FilteringCriterion $criterion): int
public function count(FilteringCriterion $criterion, ?int $limit = null): int
{
$query = $this->buildQuery($criterion);

$query->select($this->getDatabasePlatform()->getCountExpression('DISTINCT location.node_id'));

$query = $this->wrapCountQuery(
$query,
'location.node_id',
$limit
);

return (int)$query->execute()->fetch(FetchMode::COLUMN);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ function (array $row): ContentItem {
return $list;
}

public function count(Filter $filter): int
public function count(Filter $filter, ?int $limit = null): int
{
return $this->gateway->count($filter->getCriterion());
return $this->gateway->count($filter->getCriterion(), $limit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ function (array $row): LocationWithContentInfo {
return $list;
}

public function count(Filter $filter): int
public function count(Filter $filter, ?int $limit = null): int
{
return $this->gateway->count($filter->getCriterion());
return $this->gateway->count($filter->getCriterion(), $limit);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\Persistence\Legacy\Traits\Doctrine;

use Doctrine\DBAL\Query\QueryBuilder;

/**
* Limited Count count trait. Used to allow for proper limiting of count queries
* when using Doctrine DBAL QueryBuilder.
*/
trait LimitedCountQueryTrait
{
/**
* Takes a QueryBuilder and wraps it in a count query.
* This performs the following transformation to the passed query
* SELECT DISTINCT COUNT(DISTINCT someField) FROM XXX WHERE YYY;
* To
* SELECT COUNT(*) FROM (SELECT DISTINCT someField FROM XXX WHERE YYY LIMIT N) AS csub;.
*
* @param \Doctrine\DBAL\Query\QueryBuilder $queryBuilder
* @param string $countableField
* @param mixed $limit
*
* @return \Doctrine\DBAL\Query\QueryBuilder
*/
protected function wrapCountQuery(
QueryBuilder $queryBuilder,
string $countableField,
?int $limit,
): QueryBuilder {
$useLimit = $limit !== null && $limit > 0;

if (!$useLimit) {
return $queryBuilder;
}

$querySql = $queryBuilder->select($countableField)
->setMaxResults($limit)
->getSQL();

$countQuery = $this->connection->createQueryBuilder();

return $countQuery
->select(
$queryBuilder->getConnection()->getDatabasePlatform()->getCountExpression('*')
)
->from('(' . $querySql . ')', 'csub')
->setParameters($queryBuilder->getParameters(), $queryBuilder->getParameterTypes());
}
}
4 changes: 2 additions & 2 deletions eZ/Publish/Core/Repository/ContentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2626,7 +2626,7 @@ public function find(Filter $filter, ?array $languages = null): ContentList
return new ContentList($contentItemsIterator->getTotalCount(), $contentItems);
}

public function count(Filter $filter, ?array $languages = null): int
public function count(Filter $filter, ?array $languages = null, ?int $limit = null): int
{
$filter = clone $filter;
if (!empty($languages)) {
Expand All @@ -2646,6 +2646,6 @@ public function count(Filter $filter, ?array $languages = null): int
$filter->andWithCriterion($permissionCriterion);
}

return $this->contentFilteringHandler->count($filter);
return $this->contentFilteringHandler->count($filter, $limit);
}
}
Loading
Loading