Include left join with domains when listing short URLs to avoid N+1 SELECT problem

This commit is contained in:
Alejandro Celaya 2024-10-27 11:20:44 +01:00
parent 3149adebdb
commit 84a187a26f
3 changed files with 26 additions and 13 deletions

View File

@ -264,7 +264,13 @@ class ShortUrl extends AbstractEntity
return true;
}
public function toArray(?VisitsSummary $precalculatedSummary = null): array
/**
* @param null|(callable(): string|null) $getAuthority -
* This is a callback so that we trust its return value if provided, even if it is null.
* Providing the raw authority as `string|null` would result in a fallback to `$this->domain` when the authority
* was null.
*/
public function toArray(?VisitsSummary $precalculatedSummary = null, callable|null $getAuthority = null): array
{
return [
'shortCode' => $this->shortCode,
@ -276,7 +282,7 @@ class ShortUrl extends AbstractEntity
'validUntil' => $this->validUntil?->toAtomString(),
'maxVisits' => $this->maxVisits,
],
'domain' => $this->domain,
'domain' => $getAuthority !== null ? $getAuthority() : $this->domain?->authority,
'title' => $this->title,
'crawlable' => $this->crawlable,
'forwardQuery' => $this->forwardQuery,

View File

@ -9,19 +9,26 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitsSummary;
final readonly class ShortUrlWithVisitsSummary
{
private function __construct(public ShortUrl $shortUrl, private ?VisitsSummary $visitsSummary = null)
{
private function __construct(
public ShortUrl $shortUrl,
private VisitsSummary|null $visitsSummary = null,
private string|null $authority = null,
) {
}
/**
* @param array{shortUrl: ShortUrl, visits: string|int, nonBotVisits: string|int} $data
* @param array{shortUrl: ShortUrl, visits: string|int, nonBotVisits: string|int, authority: string|null} $data
*/
public static function fromArray(array $data): self
{
return new self($data['shortUrl'], VisitsSummary::fromTotalAndNonBots(
(int) $data['visits'],
(int) $data['nonBotVisits'],
));
return new self(
shortUrl: $data['shortUrl'],
visitsSummary: VisitsSummary::fromTotalAndNonBots(
total: (int) $data['visits'],
nonBots: (int) $data['nonBotVisits'],
),
authority: $data['authority'] ?? null,
);
}
public static function fromShortUrl(ShortUrl $shortUrl): self
@ -31,6 +38,6 @@ final readonly class ShortUrlWithVisitsSummary
public function toArray(): array
{
return $this->shortUrl->toArray($this->visitsSummary);
return $this->shortUrl->toArray($this->visitsSummary, fn() => $this->authority);
}
}

View File

@ -43,7 +43,7 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
$qb = $this->createListQueryBuilder($filtering);
$qb->select(
'DISTINCT s AS shortUrl',
'DISTINCT s AS shortUrl, d.authority',
'(' . $buildVisitsSubQuery('v', excludingBots: false) . ') AS ' . OrderableField::VISITS->value,
'(' . $buildVisitsSubQuery('v2', excludingBots: true) . ') AS ' . OrderableField::NON_BOT_VISITS->value,
// This is added only to have a consistent order by title between database engines
@ -89,6 +89,7 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's')
->leftJoin('s.domain', 'd')
->where('1=1');
$dateRange = $filtering->dateRange;
@ -129,8 +130,7 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
$conditions[] = $qb->expr()->like('t.name', ':searchPattern');
}
$qb->leftJoin('s.domain', 'd')
->andWhere($qb->expr()->orX(...$conditions))
$qb->andWhere($qb->expr()->orX(...$conditions))
->setParameter('searchPattern', '%' . $searchTerm . '%');
}