Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-6649: Added support for spell checking #276

Merged
merged 6 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/contracts/Repository/Values/Content/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace Ibexa\Contracts\Core\Repository\Values\Content;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Spellcheck;
use Ibexa\Contracts\Core\Repository\Values\ValueObject;

/**
Expand Down Expand Up @@ -91,11 +92,9 @@ class Query extends ValueObject
public $limit = 25;

/**
* If true spellcheck suggestions are returned.
*
* @var bool
* Spellcheck suggestions are returned.
*/
public $spellcheck;
public ?Spellcheck $spellcheck = null;

/**
* If true, search engine should perform count even if that means extra lookup.
Expand Down
26 changes: 26 additions & 0 deletions src/contracts/Repository/Values/Content/Query/Spellcheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Core\Repository\Values\Content\Query;

use Ibexa\Contracts\Core\Repository\Values\ValueObject;

final class Spellcheck extends ValueObject
{
private string $query;

public function __construct(string $query)
{
$this->query = $query;
}

public function getQuery(): string
{
return $this->query;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ class SearchResult extends ValueObject implements IteratorAggregate, Aggregation
* criterions the wrong spelled value is replaced by a corrected one (TBD).
*
* @var \Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion
*
* @deprecated since Ibexa 4.6.0, to be removed in Ibexa 5.0.0.
*/
public $spellSuggestion;

public ?SpellcheckResult $spellcheck = null;

/**
* The duration of the search processing in ms.
*
Expand Down Expand Up @@ -86,6 +90,11 @@ public function __construct(array $properties = [])
parent::__construct($properties);
}

public function getSpellcheck(): ?SpellcheckResult
{
return $this->spellcheck;
}

public function getAggregations(): ?AggregationResultCollection
{
return $this->aggregations;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Core\Repository\Values\Content\Search;

final class SpellcheckResult
{
/**
* Query with applied corrections.
*/
private ?string $query;

/**
* Flag indicating that corrections have been applied to input query.
*/
private bool $incorrect;

public function __construct(?string $query, bool $incorrect = true)
{
$this->query = $query;
$this->incorrect = $incorrect;
}

public function getQuery(): ?string
{
return $this->query;
}

public function isIncorrect(): bool
{
return $this->incorrect;
}
}
28 changes: 27 additions & 1 deletion src/lib/Pagination/Pagerfanta/AbstractSearchResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\AggregationResultCollection;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SpellcheckResult;
use Pagerfanta\Adapter\AdapterInterface;

abstract class AbstractSearchResultAdapter implements AdapterInterface, SearchResultAdapter
Expand Down Expand Up @@ -40,6 +41,8 @@ abstract class AbstractSearchResultAdapter implements AdapterInterface, SearchRe
/** @var int|null */
private $totalCount;

private ?SpellcheckResult $spellcheck = null;

public function __construct(Query $query, SearchService $searchService, array $languageFilter = [])
{
$this->query = $query;
Expand All @@ -60,9 +63,10 @@ public function getNbResults()

$countQuery = clone $this->query;
$countQuery->limit = 0;
// Skip facets/aggregations computing
// Skip facets/aggregations & spellcheck computing
$countQuery->facetBuilders = [];
$countQuery->aggregations = [];
$countQuery->spellcheck = null;

$searchResults = $this->executeQuery(
$this->searchService,
Expand Down Expand Up @@ -98,6 +102,7 @@ public function getSlice($offset, $length)
$this->time = $searchResult->time;
$this->timedOut = $searchResult->timedOut;
$this->maxScore = $searchResult->maxScore;
$this->spellcheck = $searchResult->getSpellcheck();

// Set count for further use if returned by search engine despite !performCount (Solr, ES)
if (!isset($this->totalCount) && isset($searchResult->totalCount)) {
Expand All @@ -113,6 +118,7 @@ public function getAggregations(): AggregationResultCollection
$aggregationQuery = clone $this->query;
$aggregationQuery->offset = 0;
$aggregationQuery->limit = 0;
$aggregationQuery->spellcheck = null;

$searchResults = $this->executeQuery(
$this->searchService,
Expand All @@ -126,6 +132,26 @@ public function getAggregations(): AggregationResultCollection
return $this->aggregations;
}

public function getSpellcheck(): ?SpellcheckResult
{
if ($this->spellcheck === null) {
$spellcheckQuery = clone $this->query;
$spellcheckQuery->offset = 0;
$spellcheckQuery->limit = 0;
$spellcheckQuery->aggregations = [];

$searchResults = $this->executeQuery(
$this->searchService,
$spellcheckQuery,
$this->languageFilter
);

$this->spellcheck = $searchResults->spellcheck;
}

return $this->spellcheck;
}

public function getTime(): ?float
{
return $this->time;
Expand Down
39 changes: 39 additions & 0 deletions tests/integration/Core/Repository/SearchServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use function count;
use Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException;
use Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException;
use Ibexa\Contracts\Core\Repository\SearchService;
use Ibexa\Contracts\Core\Repository\Values\Content\Content;
use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo;
use Ibexa\Contracts\Core\Repository\Values\Content\Location;
Expand Down Expand Up @@ -4658,6 +4659,44 @@ public function testFulltextLocationTranslationSearch(array $data): void
$this->assertFulltextSearchForTranslations(self::FIND_LOCATION_METHOD, $query);
}

public function testSpellcheckWithIncorrectQuery(): void
{
$searchService = $this->getRepository()->getSearchService();

if (!$searchService->supports(SearchService::CAPABILITY_SPELLCHECK)) {
self::markTestSkipped("Search engine doesn't support spellchecking");
}

$query = new Query();
// Search phrase with typo: "Contatc Us" instead of "Contact Us":
$query->spellcheck = new Query\Spellcheck('Contatc Us');

$results = $searchService->findContent($query);

self::assertNotNull($results->spellcheck);
self::assertTrue($results->spellcheck->isIncorrect());
self::assertEqualsIgnoringCase('Contact Us', $results->spellcheck->getQuery());
}

public function testSpellcheckWithCorrectQuery(): void
{
$searchService = $this->getRepository()->getSearchService();

if (!$searchService->supports(SearchService::CAPABILITY_SPELLCHECK)) {
self::markTestSkipped("Search engine doesn't support spellchecking");
}

$query = new Query();
// Search phrase without typo
$query->spellcheck = new Query\Spellcheck('Ibexa Platform');

$results = $searchService->findContent($query);

self::assertNotNull($results->spellcheck);
self::assertFalse($results->spellcheck->isIncorrect());
self::assertEqualsIgnoringCase('Ibexa Platform', $results->spellcheck->getQuery());
}

/**
* Assert that query result matches the given fixture.
*
Expand Down
Loading