Skip to content

IBX-10186 Add limits to count and subtree queries #600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: 4.6
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?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 Ibexa\Contracts\Core\Future\Repository;

use Ibexa\Contracts\Core\Repository\ContentService;
use Ibexa\Contracts\Core\Repository\Values\Filter\Filter;

/**
* @internal Future version of ContentService to be released in Ibexa Core 5.0. Used to force value proxies to generate correct types. For internal use only.
*/
interface FutureContentService extends ContentService
{
/**
* {@inheritDoc}
*/
public function count(Filter $filter, ?array $languages = null, ?int $limit = null): int;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?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 Ibexa\Contracts\Core\Future\Repository;

use Ibexa\Contracts\Core\Repository\LocationService;
use Ibexa\Contracts\Core\Repository\Values\Content\Location;
use Ibexa\Contracts\Core\Repository\Values\Filter\Filter;

/**
* @internal Future version of LocationService to be released in Ibexa Core 5.0. Used to force value proxies to generate correct types. For internal use only.
*/
interface FutureLocationService extends LocationService
{
/**
* {@inheritDoc}
*/
public function getLocationChildCount(Location $location, ?int $limit = null): int;

/**
* {@inheritDoc}
*/
public function getSubtreeSize(Location $location, ?int $limit = null): int;

/**
* {@inheritDoc}
*/
public function count(Filter $filter, ?array $languages = null, ?int $limit = null): int;
}
6 changes: 5 additions & 1 deletion src/contracts/Persistence/Content/Location/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ public function loadParentLocationsForDraftContent($contentId);
*/
public function copySubtree($sourceId, $destinationParentId);

public function getSubtreeSize(string $path): int;
/**
* @param int|null $limit
*/
// @phpstan-ignore parameter.notFound
public function getSubtreeSize(string $path /* ?int $limit = null */): int;

/**
* Moves location identified by $sourceId into new parent identified by $destinationParentId.
Expand Down
7 changes: 6 additions & 1 deletion src/contracts/Persistence/Filter/Content/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ interface Handler
*/
public function find(Filter $filter): iterable;

public function count(Filter $filter): int;
/**
* @param \Ibexa\Contracts\Core\Repository\Values\Filter\Filter $filter
* @param int|null $limit
*/
// @phpstan-ignore parameter.notFound
public function count(Filter $filter /* ?int $limit = null */): int;
}

class_alias(Handler::class, 'eZ\Publish\SPI\Persistence\Filter\Content\Handler');
7 changes: 6 additions & 1 deletion src/contracts/Persistence/Filter/Location/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ interface Handler
*/
public function find(Filter $filter): iterable;

public function count(Filter $filter): int;
/**
* @param \Ibexa\Contracts\Core\Repository\Values\Filter\Filter $filter
* @param int|null $limit
*/
// @phpstan-ignore parameter.notFound
public function count(Filter $filter /* ?int $limit = null */): int;
}

class_alias(Handler::class, 'eZ\Publish\SPI\Persistence\Filter\Location\Handler');
5 changes: 4 additions & 1 deletion src/contracts/Repository/ContentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,11 @@ public function find(Filter $filter, ?array $languages = null): ContentList;
* @param array<int, 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;
// @phpstan-ignore parameter.notFound
public function count(Filter $filter, ?array $languages = null /* ?int $limit = null */): int;
}

class_alias(ContentService::class, 'eZ\Publish\API\Repository\ContentService');
10 changes: 8 additions & 2 deletions src/contracts/Repository/Decorator/ContentServiceDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,15 @@ public function find(Filter $filter, ?array $languages = null): ContentList
return $this->innerService->find($filter, $languages);
}

public function count(Filter $filter, ?array $languages = null): int
/**
* @param int|null $limit
*/
// @phpstan-ignore parameter.notFound
public function count(Filter $filter, ?array $languages = null /* ?int $limit = null */): int
{
return $this->innerService->count($filter, $languages);
$limit = func_num_args() > 2 ? func_get_arg(2) : null;

return $this->innerService->count($filter, $languages, $limit); // @phpstan-ignore arguments.count
}
}

Expand Down
30 changes: 24 additions & 6 deletions src/contracts/Repository/Decorator/LocationServiceDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,26 @@ public function loadParentLocationsForDraftContent(
return $this->innerService->loadParentLocationsForDraftContent($versionInfo, $prioritizedLanguages);
}

public function getLocationChildCount(Location $location): int
/**
* @param int|null $limit
*/
// @phpstan-ignore parameter.notFound
public function getLocationChildCount(Location $location /* ?int $limit = null */): int
{
return $this->innerService->getLocationChildCount($location);
$limit = func_num_args() > 1 ? func_get_arg(1) : null;

return $this->innerService->getLocationChildCount($location, $limit); // @phpstan-ignore arguments.count
}

public function getSubtreeSize(Location $location): int
/**
* @param int|null $limit
*/
// @phpstan-ignore parameter.notFound
public function getSubtreeSize(Location $location /* ?int $limit = null */): int
{
return $this->innerService->getSubtreeSize($location);
$limit = func_num_args() > 1 ? func_get_arg(1) : null;

return $this->innerService->getSubtreeSize($location, $limit); // @phpstan-ignore arguments.count
}

public function createLocation(
Expand Down Expand Up @@ -160,9 +172,15 @@ public function find(Filter $filter, ?array $languages = null): LocationList
return $this->innerService->find($filter, $languages);
}

public function count(Filter $filter, ?array $languages = null): int
/**
* @param int|null $limit
*/
// @phpstan-ignore parameter.notFound
public function count(Filter $filter, ?array $languages = null /* ?int $limit = null */): int
{
return $this->innerService->count($filter, $languages);
$limit = func_num_args() > 2 ? func_get_arg(2) : null;

return $this->innerService->count($filter, $languages, $limit); // @phpstan-ignore arguments.count
}
}

Expand Down
14 changes: 11 additions & 3 deletions src/contracts/Repository/LocationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,22 @@ public function loadParentLocationsForDraftContent(VersionInfo $versionInfo, ?ar
* Returns the number of children which are readable by the current user of a location object.
*
* @param \Ibexa\Contracts\Core\Repository\Values\Content\Location $location
* @param int|null $limit If set, the count will be limited to first $limit items found.
*
* @return int
*/
public function getLocationChildCount(Location $location): int;
// @phpstan-ignore parameter.notFound
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 int|null $limit
*/
public function getSubtreeSize(Location $location): int;
// @phpstan-ignore parameter.notFound
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 @@ -274,8 +279,11 @@ public function find(Filter $filter, ?array $languages = null): LocationList;
* @param array<int, string>|null $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;
// @phpstan-ignore parameter.notFound
public function count(Filter $filter, ?array $languages = null /* ?int $limit = null */): int;
}

class_alias(LocationService::class, 'eZ\Publish\API\Repository\LocationService');
10 changes: 8 additions & 2 deletions src/lib/Persistence/Cache/LocationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,19 @@ public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
return $this->persistenceHandler->locationHandler()->copySubtree($sourceId, $destinationParentId, $newOwnerId);
}

public function getSubtreeSize(string $path): int
/**
* {@inheritdoc}
*/
public function getSubtreeSize(string $path /* ?int $limit = null */): int
{
$limit = func_num_args() > 1 ? func_get_arg(1) : null;
$this->logger->logCall(__METHOD__, [
'path' => $path,
'limit' => $limit,
]);

return $this->persistenceHandler->locationHandler()->getSubtreeSize($path);
// @phpstan-ignore-next-line
return $this->persistenceHandler->locationHandler()->getSubtreeSize($path, $limit); /* @phpstan-ignore arguments.count */
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Persistence/Legacy/Content/Location/Gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ abstract public function getSubtreeContent(int $sourceId, bool $onlyIds = false)
*/
abstract public function getSubtreeChildrenDraftContentIds(int $sourceId): 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 @@ -20,6 +20,7 @@
use Ibexa\Core\Persistence\Legacy\Content\Gateway as ContentGateway;
use Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator;
use Ibexa\Core\Persistence\Legacy\Content\Location\Gateway;
use Ibexa\Core\Persistence\Legacy\Filter\Query\LimitedCountQueryBuilder;
use Ibexa\Core\Search\Legacy\Content\Common\Gateway\CriteriaConverter;
use Ibexa\Core\Search\Legacy\Content\Common\Gateway\SortClauseConverter;
use PDO;
Expand Down Expand Up @@ -50,20 +51,25 @@ final class DoctrineDatabase extends Gateway
/** @var \Ibexa\Core\Search\Legacy\Content\Common\Gateway\SortClauseConverter */
private $trashSortClauseConverter;

/** @var \Ibexa\Core\Persistence\Legacy\Filter\Query\LimitedCountQueryBuilder */
private $limitedCountQueryBuilder;

/**
* @throws \Doctrine\DBAL\DBALException
*/
public function __construct(
Connection $connection,
MaskGenerator $languageMaskGenerator,
CriteriaConverter $trashCriteriaConverter,
SortClauseConverter $trashSortClauseConverter
SortClauseConverter $trashSortClauseConverter,
LimitedCountQueryBuilder $limitedCountQueryBuilder
) {
$this->connection = $connection;
$this->dbPlatform = $this->connection->getDatabasePlatform();
$this->languageMaskGenerator = $languageMaskGenerator;
$this->trashCriteriaConverter = $trashCriteriaConverter;
$this->trashSortClauseConverter = $trashSortClauseConverter;
$this->limitedCountQueryBuilder = $limitedCountQueryBuilder;
}

public function getBasicNodeData(
Expand Down Expand Up @@ -260,7 +266,10 @@ public function getSubtreeChildrenDraftContentIds(int $sourceId): array
return $statement->fetchFirstColumn();
}

public function getSubtreeSize(string $path): int
/**
* @phpstan-param positive-int $limit
*/
public function getSubtreeSize(string $path, ?int $limit = null): int
{
$query = $this->createNodeQueryBuilder([$this->dbPlatform->getCountExpression('node_id')]);
$query->andWhere(
Expand All @@ -272,6 +281,12 @@ public function getSubtreeSize(string $path): int
)
);

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ public function getSubtreeChildrenDraftContentIds(int $sourceId): 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
4 changes: 2 additions & 2 deletions src/lib/Persistence/Legacy/Content/Location/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,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 @@ -22,6 +22,7 @@
use Ibexa\Core\Persistence\Legacy\Content\Gateway as ContentGateway;
use Ibexa\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
use Ibexa\Core\Persistence\Legacy\Filter\Gateway\Gateway;
use Ibexa\Core\Persistence\Legacy\Filter\Query\LimitedCountQueryBuilder;
use function iterator_to_array;
use function sprintf;
use Traversable;
Expand Down Expand Up @@ -68,14 +69,19 @@ final class DoctrineGateway implements Gateway
/** @var \Ibexa\Contracts\Core\Persistence\Filter\SortClauseVisitor */
private $sortClauseVisitor;

/** @var \Ibexa\Core\Persistence\Legacy\Filter\Query\LimitedCountQueryBuilder */
private $limitedCountQueryBuilder;

public function __construct(
Connection $connection,
CriterionVisitor $criterionVisitor,
SortClauseVisitor $sortClauseVisitor
SortClauseVisitor $sortClauseVisitor,
LimitedCountQueryBuilder $limitedCountQueryBuilder
) {
$this->connection = $connection;
$this->criterionVisitor = $criterionVisitor;
$this->sortClauseVisitor = $sortClauseVisitor;
$this->limitedCountQueryBuilder = $limitedCountQueryBuilder;
}

private function getDatabasePlatform(): AbstractPlatform
Expand All @@ -87,13 +93,22 @@ private function getDatabasePlatform(): AbstractPlatform
}
}

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

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

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

Expand Down
4 changes: 2 additions & 2 deletions src/lib/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 a limit is passed).
*/
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
Loading
Loading