Drastically improved performance when creating new short URLs with findIfExists by moving logic to DB query

This commit is contained in:
Alejandro Celaya
2020-09-23 00:22:29 +02:00
parent 8d438aa6aa
commit 460ca032d2
7 changed files with 98 additions and 110 deletions

View File

@@ -5,13 +5,16 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
use function array_column;
use function array_key_exists;
use function count;
use function Functional\contains;
class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface
@@ -196,4 +199,61 @@ DQL;
return $qb;
}
public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('s')
->from(ShortUrl::class, 's')
->where($qb->expr()->eq('s.longUrl', ':longUrl'))
->setParameter('longUrl', $url)
->setMaxResults(1);
if ($meta->hasCustomSlug()) {
$qb->andWhere($qb->expr()->eq('s.shortCode', ':slug'))
->setParameter('slug', $meta->getCustomSlug());
}
if ($meta->hasMaxVisits()) {
$qb->andWhere($qb->expr()->eq('s.maxVisits', ':maxVisits'))
->setParameter('maxVisits', $meta->getMaxVisits());
}
if ($meta->hasValidSince()) {
$qb->andWhere($qb->expr()->eq('s.validSince', ':validSince'))
->setParameter('validSince', $meta->getValidSince());
}
if ($meta->hasValidUntil()) {
$qb->andWhere($qb->expr()->eq('s.validUntil', ':validUntil'))
->setParameter('validUntil', $meta->getValidUntil());
}
if ($meta->hasDomain()) {
$qb->join('s.domain', 'd')
->andWhere($qb->expr()->eq('d.authority', ':domain'))
->setParameter('domain', $meta->getDomain());
}
$tagsAmount = count($tags);
if ($tagsAmount === 0) {
return $qb->getQuery()->getOneOrNullResult();
}
foreach ($tags as $index => $tag) {
$alias = 't_' . $index;
$qb->join('s.tags', $alias, Join::WITH, $qb->expr()->eq($alias . '.name', ':tag' . $index))
->setParameter('tag' . $index, $tag);
}
// If tags where provided, we need an extra join to see the amount of tags that every short URL has, so that we
// can discard those that also have more tags, making sure only those fully matching are included.
$qb->addSelect('COUNT(t.id) as tagsAmount')
->join('s.tags', 't')
->groupBy('s')
->having($qb->expr()->eq('tagsAmount', ':tagsAmount'))
->setParameter('tagsAmount', $tagsAmount);
$result = $qb->getQuery()->getOneOrNullResult() ?? [];
return $result[0] ?? null;
}
}

View File

@@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\Persistence\ObjectRepository;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
interface ShortUrlRepositoryInterface extends ObjectRepository
@@ -27,4 +28,6 @@ interface ShortUrlRepositoryInterface extends ObjectRepository
public function findOne(string $shortCode, ?string $domain = null): ?ShortUrl;
public function shortCodeIsInUse(string $slug, ?string $domain): bool;
public function findOneMatching(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl;
}