mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-24 13:49:03 +03:00
Load visits and nonBotVisits via sub-queries in ShortUrlListRepository
This commit is contained in:
parent
7d415e40b2
commit
7afd3fd6a2
4 changed files with 53 additions and 39 deletions
|
@ -38,4 +38,6 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
$builder->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||
->addJoinColumn('short_url_id', 'id', onDelete: 'CASCADE')
|
||||
->build();
|
||||
|
||||
$builder->addUniqueConstraint(['short_url_id', 'potential_bot', 'slot_id'], 'UQ_slot_per_short_url');
|
||||
};
|
||||
|
|
|
@ -30,30 +30,29 @@ final class Version20240318084804 extends AbstractMigration
|
|||
$botsCount = $visitsQb->setParameter('potential_bot', '1')->executeQuery()->fetchOne();
|
||||
$nonBotsCount = $visitsQb->setParameter('potential_bot', '0')->executeQuery()->fetchOne();
|
||||
|
||||
$this->connection->createQueryBuilder()
|
||||
->insert('short_url_visits_counts')
|
||||
->values([
|
||||
'short_url_id' => ':short_url_id',
|
||||
'count' => ':count',
|
||||
'potential_bot' => '1',
|
||||
])
|
||||
->setParameters([
|
||||
'short_url_id' => $shortUrlId,
|
||||
'count' => $botsCount,
|
||||
])
|
||||
->executeStatement();
|
||||
$this->connection->createQueryBuilder()
|
||||
->insert('short_url_visits_counts')
|
||||
->values([
|
||||
'short_url_id' => ':short_url_id',
|
||||
'count' => ':count',
|
||||
'potential_bot' => '0',
|
||||
])
|
||||
->setParameters([
|
||||
'short_url_id' => $shortUrlId,
|
||||
'count' => $nonBotsCount,
|
||||
])
|
||||
->executeStatement();
|
||||
if ($botsCount > 0) {
|
||||
$this->insertCount($shortUrlId, $botsCount, potentialBot: true);
|
||||
}
|
||||
if ($nonBotsCount > 0) {
|
||||
$this->insertCount($shortUrlId, $nonBotsCount, potentialBot: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function insertCount(string $shortUrlId, int $count, bool $potentialBot): void
|
||||
{
|
||||
$this->connection->createQueryBuilder()
|
||||
->insert('short_url_visits_counts')
|
||||
->values([
|
||||
'short_url_id' => ':short_url_id',
|
||||
'count' => ':count',
|
||||
'potential_bot' => ':potential_bot',
|
||||
])
|
||||
->setParameters([
|
||||
'short_url_id' => $shortUrlId,
|
||||
'count' => $count,
|
||||
'potential_bot' => $potentialBot ? '1' : '0',
|
||||
])
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@ final readonly class ShortUrlWithVisitsSummary
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array{shortUrl: ShortUrl, visitsCount: string|int, nonBotVisitsCount: string|int} $data
|
||||
* @param array{shortUrl: ShortUrl, visits: string|int, nonBotVisits: string|int} $data
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self($data['shortUrl'], VisitsSummary::fromTotalAndNonBots(
|
||||
(int) $data['visitsCount'],
|
||||
(int) $data['nonBotVisitsCount'],
|
||||
(int) $data['visits'],
|
||||
(int) $data['nonBotVisits'],
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlWithVisitsSummary;
|
|||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\ShortUrlVisitsCount;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
||||
|
@ -27,21 +28,33 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
|||
*/
|
||||
public function findList(ShortUrlsListFiltering $filtering): array
|
||||
{
|
||||
$buildVisitsSubQuery = function (string $alias, bool $excludingBots): string {
|
||||
$vqb = $this->getEntityManager()->createQueryBuilder();
|
||||
$vqb->select('SUM(' . $alias . '.count)')
|
||||
->from(ShortUrlVisitsCount::class, $alias)
|
||||
->where($vqb->expr()->eq($alias . '.shortUrl', 's'));
|
||||
|
||||
if ($excludingBots) {
|
||||
$vqb->andWhere($vqb->expr()->eq($alias . '.potentialBot', ':potentialBot'));
|
||||
}
|
||||
|
||||
return $vqb->getDQL();
|
||||
};
|
||||
|
||||
$qb = $this->createListQueryBuilder($filtering);
|
||||
$qb->select('DISTINCT s AS shortUrl', 'SUM(v.count) AS visitsCount', 'SUM(v2.count) AS nonBotVisitsCount')
|
||||
->addSelect('SUM(v.count)')
|
||||
->leftJoin('s.visitsCounts', 'v')
|
||||
->leftJoin('s.visitsCounts', 'v2', Join::WITH, $qb->expr()->andX(
|
||||
$qb->expr()->eq('v.shortUrl', 's'),
|
||||
$qb->expr()->eq('v.potentialBot', 'false'),
|
||||
))
|
||||
->groupBy('s')
|
||||
$qb->select(
|
||||
'DISTINCT s AS shortUrl',
|
||||
'(' . $buildVisitsSubQuery('v', excludingBots: false) . ') AS ' . OrderableField::VISITS->value,
|
||||
'(' . $buildVisitsSubQuery('v2', excludingBots: true) . ') AS ' . OrderableField::NON_BOT_VISITS->value,
|
||||
)
|
||||
->setMaxResults($filtering->limit)
|
||||
->setFirstResult($filtering->offset);
|
||||
->setFirstResult($filtering->offset)
|
||||
// This param is used in one of the sub-queries, but needs to set in the parent query
|
||||
->setParameter('potentialBot', 0);
|
||||
|
||||
$this->processOrderByForList($qb, $filtering);
|
||||
|
||||
/** @var array{shortUrl: ShortUrl, visitsCount: string, nonBotVisitsCount: string}[] $result */
|
||||
/** @var array{shortUrl: ShortUrl, visits: string, nonBotVisits: string}[] $result */
|
||||
$result = $qb->getQuery()->getResult();
|
||||
return map($result, static fn (array $s) => ShortUrlWithVisitsSummary::fromArray($s));
|
||||
}
|
||||
|
@ -54,8 +67,8 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
|||
match (true) {
|
||||
// With no explicit order by, fallback to dateCreated-DESC
|
||||
$fieldName === null => $qb->orderBy('s.dateCreated', 'DESC'),
|
||||
$fieldName === OrderableField::VISITS->value => $qb->orderBy('SUM(v.count)', $order),
|
||||
$fieldName === OrderableField::NON_BOT_VISITS->value => $qb->orderBy('SUM(v2.count)', $order),
|
||||
$fieldName === OrderableField::VISITS->value,
|
||||
$fieldName === OrderableField::NON_BOT_VISITS->value => $qb->orderBy($fieldName, $order),
|
||||
default => $qb->orderBy('s.' . $fieldName, $order),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue