diff --git a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php index e4061cea1..6b8139419 100644 --- a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php +++ b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php @@ -27,7 +27,7 @@ class SearchUserApiProvider implements SearchApiInterface ->setSelectPertinence("GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical), SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))", [ $pattern, $pattern ]) ->setFromClause("users AS u") - ->setWhereClause("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 + ->setWhereClauses("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 OR SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15 ", [ $pattern, $pattern ]); diff --git a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php index 2c986ee6c..9188c0b5c 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php +++ b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php @@ -12,8 +12,8 @@ class SearchApiQuery private array $pertinenceParams = []; private ?string $fromClause = null; private array $fromClauseParams = []; - private ?string $whereClause = null; - private array $whereClauseParams = []; + private array $whereClauses = []; + private array $whereClausesParams = []; public function setSelectKey(string $selectKey, array $params = []): self { @@ -47,16 +47,39 @@ class SearchApiQuery return $this; } - public function setWhereClause(string $whereClause, array $params = []): self + /** + * Set the where clause and replace all existing ones. + * + */ + public function setWhereClauses(string $whereClause, array $params = []): self { - $this->whereClause = $whereClause; - $this->whereClauseParams = $params; + $this->whereClauses = [$whereClause]; + $this->whereClausesParams = [$params]; + + return $this; + } + + /** + * Add a where clause. + * + * This will add to previous where clauses with and `AND` join + * + * @param string $whereClause + * @param array $params + * @return $this + */ + public function andWhereClause(string $whereClause, array $params = []): self + { + $this->whereClauses[] = $whereClause; + $this->whereClausesParams[] = $params; return $this; } public function buildQuery(): string { + $where = \implode(' AND ', $this->whereClauses); + return \strtr("SELECT '{key}' AS key, {metadata} AS metadata, @@ -68,7 +91,7 @@ class SearchApiQuery '{metadata}' => $this->jsonbMetadata, '{pertinence}' => $this->pertinence, '{from}' => $this->fromClause, - '{where}' => $this->whereClause, + '{where}' => $where, ]); } @@ -79,7 +102,7 @@ class SearchApiQuery $this->jsonbMetadataParams, $this->pertinenceParams, $this->fromClauseParams, - $this->whereClauseParams, + \array_merge([], ...$this->whereClausesParams), ); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php new file mode 100644 index 000000000..2e63f24e0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php @@ -0,0 +1,40 @@ +setSelectJsonbMetadata('boum') + ->setSelectKey('bim') + ->setSelectPertinence('1') + ->setFromClause('badaboum') + ->andWhereClause('foo', [ 'alpha' ]) + ->andWhereClause('bar', [ 'beta' ]) + ; + + $query = $q->buildQuery(); + + $this->assertStringContainsString('foo AND bar', $query); + $this->assertEquals(['alpha', 'beta'], $q->buildParameters()); + } + + public function testWithoutWhereClause() + { + $q = new SearchApiQuery(); + $q->setSelectJsonbMetadata('boum') + ->setSelectKey('bim') + ->setSelectPertinence('1') + ->setFromClause('badaboum') + ; + + $this->assertTrue(\is_string($q->buildQuery())); + $this->assertEquals([], $q->buildParameters()); + } + +} diff --git a/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php b/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php index 4d1b720db..dd0ae67c7 100644 --- a/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php +++ b/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php @@ -26,7 +26,7 @@ class SearchPersonApiProvider implements SearchApiInterface "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int". ")", [ $pattern, $pattern ]) ->setFromClause("chill_person_person AS person") - ->setWhereClause("LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ". + ->setWhereClauses("LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ". "person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' ", [ $pattern, $pattern ]) ; diff --git a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php index 47d5cc6b9..d25b6ac8f 100644 --- a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php +++ b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php @@ -5,7 +5,36 @@ namespace Chill\ThirdPartyBundle\Search; use Chill\MainBundle\Search\SearchApiInterface; use Chill\MainBundle\Search\SearchApiQuery; use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; +use function explode; + +/* + * Internal note: test query for parametrizing / testing: + * +WITH rows AS ( + SELECT 'aide a domicile en milieu rural admr' AS c, 'la roche sur yon' AS l + UNION + SELECT 'aide a domicile en milieu rural admr' AS c, 'fontenay-le-comte' AS l +), searches AS ( + SELECT 'admr roche' AS s, 'admr' AS s1, 'roche' As s2 + UNION + SELECT 'admr font' AS s, 'admr' AS s1, 'font' AS s2 +) +SELECT + c, l, s, s1, s2, + strict_word_similarity(s, c) + + (c LIKE '%' || s1 || '%')::int + + (c LIKE '%' || s2 || '%')::int + + (l LIKE '%' || s1 || '%')::int + + (l LIKE '%' || s2 || '%')::int, + l LIKE '%' || s1 || '%', + l LIKE '%' || s2 || '%' +FROM rows, searches + */ + +/** + * Generate query for searching amongst third parties + */ class ThirdPartyApiSearch implements SearchApiInterface { private ThirdPartyRepository $thirdPartyRepository; @@ -17,18 +46,45 @@ class ThirdPartyApiSearch implements SearchApiInterface public function provideQuery(string $pattern, array $parameters): SearchApiQuery { - return (new SearchApiQuery) + $query = (new SearchApiQuery) ->setSelectKey('tparty') ->setSelectJsonbMetadata("jsonb_build_object('id', tparty.id)") - ->setSelectPertinence("GREATEST(". - "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized),". - "(tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')::int". - ")", [ $pattern, $pattern ]) - ->setFromClause('chill_3party.third_party AS tparty') - ->setWhereClause("tparty.active IS TRUE ". - "AND (LOWER(UNACCENT(?)) <<% tparty.canonicalized OR ". - "tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')", [ $pattern, $pattern ]) - ; + ->setFromClause('chill_3party.third_party AS tparty + LEFT JOIN chill_main_address cma ON cma.id = tparty.address_id + LEFT JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id + LEFT JOIN chill_3party.third_party AS parent ON tparty.parent_id = parent.id + LEFT JOIN chill_main_address cma_p ON parent.address_id = cma_p.id + LEFT JOIN chill_main_postal_code cmpc_p ON cma_p.postcode_id = cmpc.id') + ->andWhereClause("tparty.active IS TRUE") + ; + + $strs = explode(' ', $pattern); + $wheres = []; + $whereArgs = []; + $pertinence = []; + $pertinenceArgs = []; + + foreach ($strs as $str) { + if (!empty($str)) { + $wheres[] = "(LOWER(UNACCENT(?)) <<% tparty.canonicalized OR + tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')"; + $whereArgs[] = [$str, $str]; + $pertinence[] = "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized) + ". + "(tparty.canonicalized LIKE '%s' || LOWER(UNACCENT(?)) || '%')::int + ". + // take postcode label into account, but lower than the canonicalized field + "COALESCE((LOWER(UNACCENT(cmpc.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0) + ". + "COALESCE((LOWER(UNACCENT(cmpc_p.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0)"; + $pertinenceArgs[] = [$str, $str, $str, $str]; + } + } + + $query + ->setSelectPertinence(\implode(' + ', $pertinence), \array_merge([], + ...$pertinenceArgs)) + ->andWhereClause(\implode(' OR ', $wheres), \array_merge([], + ...$whereArgs)); + + return $query; } public function supportsTypes(string $pattern, array $types, array $parameters): bool