Skip to content
Open
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"php": "^7.4 || ^8.0",
"ext-json": "*",
"ext-xmlwriter": "*",
"ibexa/core": "~4.6.0@dev",
"ibexa/core": "dev-taxonomy-suggestions as 4.6.x-dev",
"netgen/query-translator": "^1.0.2",
"symfony/http-kernel": "^5.0",
"symfony/dependency-injection": "^5.0",
Expand Down
18 changes: 18 additions & 0 deletions src/contracts/Query/EmbeddingVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?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\Solr\Query;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Embedding;

abstract class EmbeddingVisitor
{
abstract public function canVisit(Embedding $embedding): bool;

abstract public function visit(Embedding $embedding, int $limit): string;
}
59 changes: 59 additions & 0 deletions src/lib/Query/Common/EmbeddingVisitor/Aggregate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?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\Solr\Query\Common\EmbeddingVisitor;

use Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Embedding;
use Ibexa\Contracts\Solr\Query\EmbeddingVisitor;

final class Aggregate extends EmbeddingVisitor
{
/** @var iterable<\Ibexa\Contracts\Solr\Query\EmbeddingVisitor> */
protected iterable $visitors = [];

/**
* @param \Ibexa\Contracts\Solr\Query\EmbeddingVisitor[] $visitors
*/
public function __construct(iterable $visitors = [])
{
$this->visitors = $visitors;
}

public function canVisit(Embedding $embedding): bool
{
return $this->findVisitor($embedding) !== null;
}

/**
* Map field value to a proper Solr representation.
*
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException
*/
public function visit(Embedding $embedding, int $limit): string
{
foreach ($this->visitors as $visitor) {
if ($visitor->canVisit($embedding)) {
return $visitor->visit($embedding, $limit);
}
}

throw new NotImplementedException('No visitor available for: ' . get_class($embedding));
}

private function findVisitor(Embedding $embedding): ?EmbeddingVisitor
{
foreach ($this->visitors as $visitor) {
if ($visitor->canVisit($embedding)) {
return $visitor;
}
}

return null;
}
}
14 changes: 12 additions & 2 deletions src/lib/Query/Common/QueryConverter/NativeQueryConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
*/
namespace Ibexa\Solr\Query\Common\QueryConverter;

use Ibexa\Contracts\Core\Repository\Values\Content\EmbeddingQuery;
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Solr\Query\AggregationVisitor;
use Ibexa\Contracts\Solr\Query\CriterionVisitor;
use Ibexa\Contracts\Solr\Query\EmbeddingVisitor;
use Ibexa\Contracts\Solr\Query\SortClauseVisitor;
use Ibexa\Solr\Query\FacetFieldVisitor;
use Ibexa\Solr\Query\QueryConverter;
Expand Down Expand Up @@ -44,6 +46,8 @@ class NativeQueryConverter extends QueryConverter
*/
private $aggregationVisitor;

private EmbeddingVisitor $embeddingVisitor;

/**
* Construct from visitors.
*
Expand All @@ -55,26 +59,32 @@ public function __construct(
CriterionVisitor $criterionVisitor,
SortClauseVisitor $sortClauseVisitor,
FacetFieldVisitor $facetBuilderVisitor,
AggregationVisitor $aggregationVisitor
AggregationVisitor $aggregationVisitor,
EmbeddingVisitor $embeddingVisitor
) {
$this->criterionVisitor = $criterionVisitor;
$this->sortClauseVisitor = $sortClauseVisitor;
$this->facetBuilderVisitor = $facetBuilderVisitor;
$this->aggregationVisitor = $aggregationVisitor;
$this->embeddingVisitor = $embeddingVisitor;
}

public function convert(Query $query, array $languageSettings = [])
{
$params = [
'q' => '{!lucene}' . $this->criterionVisitor->visit($query->query),
'fq' => '{!lucene}' . $this->criterionVisitor->visit($query->filter),
'fq' => ['{!lucene}' . $this->criterionVisitor->visit($query->filter)],
'sort' => $this->getSortClauses($query->sortClauses),
'start' => $query->offset,
'rows' => $query->limit,
'fl' => '*,score,[shard]',
'wt' => 'json',
];

if ($query instanceof EmbeddingQuery && $query->getEmbedding() !== null) {
$params['fq'][] = $this->embeddingVisitor->visit($query->getEmbedding(), $query->limit);
}

$facetParams = $this->getFacetParams($query->facetBuilders);
if (!empty($facetParams)) {
$params['facet'] = 'true';
Expand Down
2 changes: 2 additions & 0 deletions src/lib/Resources/config/container/solr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ services:
- '@ibexa.solr.query.content.sort_clause_visitor.aggregate'
- '@ibexa.solr.query.content.facet_builder_visitor.aggregate'
- '@ibexa.solr.query.content.aggregation_visitor.dispatcher'
- '@Ibexa\Solr\Query\Common\EmbeddingVisitor\Aggregate'

ibexa.solr.query_converter.location:
class: Ibexa\Solr\Query\Common\QueryConverter\NativeQueryConverter
Expand All @@ -108,6 +109,7 @@ services:
- '@ibexa.solr.query.location.sort_clause_visitor.aggregate'
- '@ibexa.solr.query.location.facet_builder_visitor.aggregate'
- '@ibexa.solr.query.location.aggregation_visitor.dispatcher'
- '@Ibexa\Solr\Query\Common\EmbeddingVisitor\Aggregate'

Ibexa\Solr\Gateway\UpdateSerializer:
arguments:
Expand Down
4 changes: 4 additions & 0 deletions src/lib/Resources/config/container/solr/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ services:
arguments:
$client: '@ibexa.solr.http_client'

Ibexa\Solr\Query\Common\EmbeddingVisitor\Aggregate:
arguments:
$visitors: !tagged ibexa.search.solr.query.content.embedding.visitor

# Note: services tagged with 'ibexa.search.solr.query.content.criterion.visitor'
# are registered to this one using compilation pass
ibexa.solr.query.content.criterion_visitor.aggregate:
Expand Down
79 changes: 79 additions & 0 deletions tests/lib/Search/Query/Common/EmbeddingVisitor/AggregateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?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\Tests\Solr\Search\Query\Common\EmbeddingVisitor;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Embedding;
use Ibexa\Contracts\Solr\Query\EmbeddingVisitor;
use Ibexa\Solr\Query\Common\EmbeddingVisitor\Aggregate;
use Ibexa\Tests\Solr\Search\TestCase;

final class AggregateTest extends TestCase
{
private const EXAMPLE_VISITOR_RESULT = '{!knn f=field topK=3[0,1]';

public function testCanVisitOnSupportedEmbedding(): void
{
$embedding = $this->createMock(Embedding::class);

$dispatcher = new Aggregate([
$this->createVisitorMock($embedding, false),
$this->createVisitorMock($embedding, true),
$this->createVisitorMock($embedding, false),
]);

$this->assertTrue($dispatcher->canVisit($embedding));
}

public function testCanVisitOnNonSupportedEmbedding(): void
{
$embedding = $this->createMock(Embedding::class);

$dispatcher = new Aggregate([
$this->createVisitorMock($embedding, false),
$this->createVisitorMock($embedding, false),
$this->createVisitorMock($embedding, false),
]);

$this->assertFalse($dispatcher->canVisit($embedding));
}

public function testVisit(): void
{
$embedding = $this->createMock(Embedding::class);

$visitorA = $this->createVisitorMock($embedding, false);
$visitorB = $this->createVisitorMock($embedding, true);
$visitorC = $this->createVisitorMock($embedding, false);

$dispatcher = new Aggregate([$visitorA, $visitorB, $visitorC]);

$visitorB
->method('visit')
->with($embedding, 3)
->willReturn(self::EXAMPLE_VISITOR_RESULT);

$this->assertEquals(
self::EXAMPLE_VISITOR_RESULT,
$dispatcher->visit($embedding, 3)
);
}

/**
* @return \PHPUnit\Framework\MockObject\MockObject&\Ibexa\Contracts\Solr\Query\EmbeddingVisitor
*/
private function createVisitorMock(
Embedding $embedding,
bool $supports
): EmbeddingVisitor {
$visitor = $this->createMock(EmbeddingVisitor::class);
$visitor->method('canVisit')->with($embedding)->willReturn($supports);

return $visitor;
}
}
Loading