optimize query for person

This commit is contained in:
Julien Fastré 2021-09-10 18:22:50 +02:00
parent 6bc83edfe9
commit f63d4fcfba
6 changed files with 82 additions and 25 deletions

View File

@ -19,6 +19,7 @@
namespace Chill\MainBundle\DependencyInjection; namespace Chill\MainBundle\DependencyInjection;
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Form\UserJobType; use Chill\MainBundle\Form\UserJobType;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -185,6 +186,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class, 'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,
'SIMILARITY' => Similarity::class, 'SIMILARITY' => Similarity::class,
'OVERLAPSI' => OverlapsI::class, 'OVERLAPSI' => OverlapsI::class,
'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class
], ],
], ],
'hydrators' => [ 'hydrators' => [

View File

@ -21,20 +21,15 @@ namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Lexer;
/**
*
*
*
*/
class Similarity extends FunctionNode class Similarity extends FunctionNode
{ {
private $firstPart; private $firstPart;
private $secondPart; private $secondPart;
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{ {
return 'SIMILARITY('.$this->firstPart->dispatch($sqlWalker). return 'SIMILARITY('.$this->firstPart->dispatch($sqlWalker).
', ' . $this->secondPart->dispatch($sqlWalker) .")"; ', ' . $this->secondPart->dispatch($sqlWalker) .")";
} }
@ -42,13 +37,13 @@ class Similarity extends FunctionNode
{ {
$parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstPart = $parser->StringPrimary(); $this->firstPart = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA); $parser->match(Lexer::T_COMMA);
$this->secondPart = $parser->StringPrimary(); $this->secondPart = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS);
} }
} }

View File

@ -0,0 +1,34 @@
<?php
namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
class StrictWordSimilarityOPS extends \Doctrine\ORM\Query\AST\Functions\FunctionNode
{
private $firstPart;
private $secondPart;
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return $this->firstPart->dispatch($sqlWalker).
' <<% ' . $this->secondPart->dispatch($sqlWalker);
}
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstPart = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->secondPart = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -12,7 +12,6 @@ use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException; use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@ -126,7 +125,7 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
).'AS text' ).'AS text'
); );
} else { } else {
$qb->select('p'); $qb->select('sp');
} }
$qb $qb
@ -254,18 +253,14 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
$grams = explode(' ', $pattern); $grams = explode(' ', $pattern);
foreach($grams as $key => $gram) { foreach($grams as $key => $gram) {
$qb->andWhere('SIMILARITY(sp.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.')) ) >= 0.15') $qb->andWhere('STRICT_WORD_SIMILARITY_OPS(:default_'.$key.', sp.fullnameCanonical) = TRUE')
->setParameter('default_'.$key, '%'.$gram.'%'); ->setParameter('default_'.$key, '%'.$gram.'%');
}
$qb->andWhere($qb->expr() // remove the perfect matches
->notIn( $qb->andWhere($qb->expr()
'sp.id', ->notLike('sp.fullnameCanonical', 'UNACCENT(LOWER(:not_default_'.$key.'))'))
$this->createSearchQuery($pattern) ->setParameter('not_default_'.$key, '%'.$gram.'%');
->addSelect('p.id') }
->getDQL()
)
);
return $qb; return $qb;
} }

View File

@ -104,7 +104,7 @@ class SimilarityPersonSearch extends AbstractSearch
protected function search(array $terms, $start, $limit, array $options = array()) protected function search(array $terms, $start, $limit, array $options = array())
{ {
return $this->personACLAwareRepository return $this->personACLAwareRepository
->findBySimilaritySearch($terms['_default']); ->findBySimilaritySearch($terms['_default'], $start, $limit, $options['simplify'] ?? false);
} }
protected function count(array $terms) protected function count(array $terms)

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Optimize trigram index on person fullname: create index for both center_id and fullname
*/
final class Version20210910161858 extends AbstractMigration
{
public function getDescription(): string
{
return 'Optimize trigram index on person fullname: create index for both center_id and fullname';
}
public function up(Schema $schema): void
{
$this->addSql('DROP INDEX fullnamecanonical_trgm_idx');
$this->addSql('CREATE INDEX fullnameCanonical_trgm_idx ON chill_person_person USING GIST (center_id, fullnameCanonical gist_trgm_ops)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX fullnamecanonical_trgm_idx');
$this->addSql('CREATE INDEX fullnameCanonical_trgm_idx ON chill_person_person USING GIST (fullnameCanonical gist_trgm_ops)');
}
}