mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-29 04:52:54 +03:00
Improved parameter definition in some private queries in VisitRepository
This commit is contained in:
parent
ae060f3b13
commit
5be882a31b
2 changed files with 42 additions and 39 deletions
|
@ -7,14 +7,11 @@ namespace Shlinkio\Shlink\Core\Repository;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||||
|
|
||||||
use function preg_replace;
|
|
||||||
|
|
||||||
use const PHP_INT_MAX;
|
use const PHP_INT_MAX;
|
||||||
|
|
||||||
class VisitRepository extends EntityRepository implements VisitRepositoryInterface
|
class VisitRepository extends EntityRepository implements VisitRepositoryInterface
|
||||||
|
@ -29,7 +26,7 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||||
->from(Visit::class, 'v')
|
->from(Visit::class, 'v')
|
||||||
->where($qb->expr()->isNull('v.visitLocation'));
|
->where($qb->expr()->isNull('v.visitLocation'));
|
||||||
|
|
||||||
return $this->findVisitsForQuery($qb, $blockSize);
|
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +42,7 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||||
->andWhere($qb->expr()->eq('vl.isEmpty', ':isEmpty'))
|
->andWhere($qb->expr()->eq('vl.isEmpty', ':isEmpty'))
|
||||||
->setParameter('isEmpty', true);
|
->setParameter('isEmpty', true);
|
||||||
|
|
||||||
return $this->findVisitsForQuery($qb, $blockSize);
|
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||||
|
@ -54,10 +51,10 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||||
$qb->select('v')
|
$qb->select('v')
|
||||||
->from(Visit::class, 'v');
|
->from(Visit::class, 'v');
|
||||||
|
|
||||||
return $this->findVisitsForQuery($qb, $blockSize);
|
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findVisitsForQuery(QueryBuilder $qb, int $blockSize): iterable
|
private function visitsIterableForQuery(QueryBuilder $qb, int $blockSize): iterable
|
||||||
{
|
{
|
||||||
$originalQueryBuilder = $qb->setMaxResults($blockSize)
|
$originalQueryBuilder = $qb->setMaxResults($blockSize)
|
||||||
->orderBy('v.id', 'ASC');
|
->orderBy('v.id', 'ASC');
|
||||||
|
@ -89,33 +86,14 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||||
?int $limit = null,
|
?int $limit = null,
|
||||||
?int $offset = null
|
?int $offset = null
|
||||||
): array {
|
): array {
|
||||||
/**
|
$qb = $this->createVisitsByShortCodeQueryBuilder($shortCode, $domain, $dateRange);
|
||||||
* @var QueryBuilder $qb
|
|
||||||
* @var ShortUrl|int $shortUrl
|
|
||||||
*/
|
|
||||||
[$qb, $shortUrl] = $this->createVisitsByShortCodeQueryBuilder($shortCode, $domain, $dateRange);
|
|
||||||
$qb->select('v.id')
|
$qb->select('v.id')
|
||||||
->orderBy('v.id', 'DESC')
|
->orderBy('v.id', 'DESC')
|
||||||
// Falling back to values that will behave as no limit/offset, but will workaround MS SQL not allowing
|
// Falling back to values that will behave as no limit/offset, but will workaround MS SQL not allowing
|
||||||
// order on sub-queries without offset
|
// order on sub-queries without offset
|
||||||
->setMaxResults($limit ?? PHP_INT_MAX)
|
->setMaxResults($limit ?? PHP_INT_MAX)
|
||||||
->setFirstResult($offset ?? 0);
|
->setFirstResult($offset ?? 0);
|
||||||
|
$subQuery = $qb->getQuery()->getSQL();
|
||||||
// FIXME Crappy way to resolve the params into the query. Best option would be to inject the sub-query with
|
|
||||||
// placeholders and then pass params to the main query
|
|
||||||
$shortUrlId = $shortUrl instanceof ShortUrl ? $shortUrl->getId() : $shortUrl;
|
|
||||||
$subQuery = preg_replace('/\?/', $shortUrlId, $qb->getQuery()->getSQL(), 1);
|
|
||||||
if ($dateRange !== null && $dateRange->getStartDate() !== null) {
|
|
||||||
$subQuery = preg_replace(
|
|
||||||
'/\?/',
|
|
||||||
'\'' . $dateRange->getStartDate()->toDateTimeString() . '\'',
|
|
||||||
$subQuery,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ($dateRange !== null && $dateRange->getEndDate() !== null) {
|
|
||||||
$subQuery = preg_replace('/\?/', '\'' . $dateRange->getEndDate()->toDateTimeString() . '\'', $subQuery, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A native query builder needs to be used here because DQL and ORM query builders do not accept
|
// A native query builder needs to be used here because DQL and ORM query builders do not accept
|
||||||
// sub-queries at "from" and "join" level.
|
// sub-queries at "from" and "join" level.
|
||||||
|
@ -140,8 +118,7 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||||
|
|
||||||
public function countVisitsByShortCode(string $shortCode, ?string $domain = null, ?DateRange $dateRange = null): int
|
public function countVisitsByShortCode(string $shortCode, ?string $domain = null, ?DateRange $dateRange = null): int
|
||||||
{
|
{
|
||||||
/** @var QueryBuilder $qb */
|
$qb = $this->createVisitsByShortCodeQueryBuilder($shortCode, $domain, $dateRange);
|
||||||
[$qb] = $this->createVisitsByShortCodeQueryBuilder($shortCode, $domain, $dateRange);
|
|
||||||
$qb->select('COUNT(v.id)');
|
$qb->select('COUNT(v.id)');
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||||
|
@ -151,26 +128,40 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa
|
||||||
string $shortCode,
|
string $shortCode,
|
||||||
?string $domain,
|
?string $domain,
|
||||||
?DateRange $dateRange
|
?DateRange $dateRange
|
||||||
): array {
|
): QueryBuilder {
|
||||||
/** @var ShortUrlRepositoryInterface $shortUrlRepo */
|
/** @var ShortUrlRepositoryInterface $shortUrlRepo */
|
||||||
$shortUrlRepo = $this->getEntityManager()->getRepository(ShortUrl::class);
|
$shortUrlRepo = $this->getEntityManager()->getRepository(ShortUrl::class);
|
||||||
$shortUrl = $shortUrlRepo->findOne($shortCode, $domain) ?? -1;
|
$shortUrl = $shortUrlRepo->findOne($shortCode, $domain);
|
||||||
|
$shortUrlId = $shortUrl !== null ? $shortUrl->getId() : -1;
|
||||||
|
|
||||||
|
// Parameters in this query need to be part of the query itself, as we need to use it a sub-query later
|
||||||
|
// Since they are not strictly provided by the caller, it's reasonably safe
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||||
$qb->from(Visit::class, 'v')
|
$qb->from(Visit::class, 'v')
|
||||||
->where($qb->expr()->eq('v.shortUrl', ':shortUrl'))
|
->where($qb->expr()->eq('v.shortUrl', $shortUrlId));
|
||||||
->setParameter('shortUrl', $shortUrl);
|
|
||||||
|
|
||||||
// Apply date range filtering
|
// Apply date range filtering
|
||||||
if ($dateRange !== null && $dateRange->getStartDate() !== null) {
|
if ($dateRange !== null && $dateRange->getStartDate() !== null) {
|
||||||
$qb->andWhere($qb->expr()->gte('v.date', ':startDate'))
|
$qb->andWhere($qb->expr()->gte('v.date', '\'' . $dateRange->getStartDate()->toDateTimeString() . '\''));
|
||||||
->setParameter('startDate', $dateRange->getStartDate(), ChronosDateTimeType::CHRONOS_DATETIME);
|
|
||||||
}
|
}
|
||||||
if ($dateRange !== null && $dateRange->getEndDate() !== null) {
|
if ($dateRange !== null && $dateRange->getEndDate() !== null) {
|
||||||
$qb->andWhere($qb->expr()->lte('v.date', ':endDate'))
|
$qb->andWhere($qb->expr()->lte('v.date', '\'' . $dateRange->getEndDate()->toDateTimeString() . '\''));
|
||||||
->setParameter('endDate', $dateRange->getEndDate(), ChronosDateTimeType::CHRONOS_DATETIME);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [$qb, $shortUrl];
|
return $qb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findVisitsByTag(
|
||||||
|
string $tag,
|
||||||
|
?DateRange $dateRange = null,
|
||||||
|
?int $limit = null,
|
||||||
|
?int $offset = null
|
||||||
|
): array {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countVisitsByTag(string $tag, ?DateRange $dateRange = null): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,4 +43,16 @@ interface VisitRepositoryInterface extends ObjectRepository
|
||||||
?string $domain = null,
|
?string $domain = null,
|
||||||
?DateRange $dateRange = null
|
?DateRange $dateRange = null
|
||||||
): int;
|
): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Visit[]
|
||||||
|
*/
|
||||||
|
public function findVisitsByTag(
|
||||||
|
string $tag,
|
||||||
|
?DateRange $dateRange = null,
|
||||||
|
?int $limit = null,
|
||||||
|
?int $offset = null
|
||||||
|
): array;
|
||||||
|
|
||||||
|
public function countVisitsByTag(string $tag, ?DateRange $dateRange = null): int;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue