Skip to content

Commit

Permalink
Search Query and more
Browse files Browse the repository at this point in the history
  • Loading branch information
ragusa87 committed Dec 11, 2024
1 parent a9209e9 commit 93f8906
Show file tree
Hide file tree
Showing 18 changed files with 309 additions and 236 deletions.
3 changes: 2 additions & 1 deletion BibliotecaTypesenseBundle/src/Client/ClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class ClientFactory
{
public function __construct(
private readonly string $uri,
#[\SensitiveParameter] private readonly string $apiKey,
#[\SensitiveParameter]
private readonly string $apiKey,
private readonly int $connectionTimeoutSeconds = 5,
) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Biblioteca\TypesenseBundle\Mapper\MapperInterface;
use Biblioteca\TypesenseBundle\Mapper\MapperLocator;
use Biblioteca\TypesenseBundle\PopulateService;
use Biblioteca\TypesenseBundle\Populate\PopulateService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
Expand Down
12 changes: 6 additions & 6 deletions BibliotecaTypesenseBundle/src/Mapper/FieldMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public function __construct(
public ?bool $drop = null,
public ?bool $index = null,
public ?bool $infix = null,
public ?bool $range_index = null,
public ?bool $rangeIndex = null,
public ?bool $sort = null, // Default depends on the type; not assigned here
public ?bool $stem = null,
public ?bool $store = null,
public ?int $num_dim = null,
public ?int $numDim = null,
public ?string $locale = null,
public ?string $reference = null,
public ?string $vec_dist = null,
public ?string $vecDist = null,
) {
$this->type = $type instanceof DataTypeEnum ? $type->value : $type;
}
Expand All @@ -40,10 +40,10 @@ public function toArray(): array
'sort' => $this->sort,
'infix' => $this->infix,
'locale' => $this->locale,
'num_dim' => $this->num_dim,
'vec_dist' => $this->vec_dist,
'num_dim' => $this->numDim,
'vec_dist' => $this->vecDist,
'reference' => $this->reference,
'range_index' => $this->range_index,
'range_index' => $this->rangeIndex,
'drop' => $this->drop,
'stem' => $this->stem,
]);
Expand Down
10 changes: 8 additions & 2 deletions BibliotecaTypesenseBundle/src/Mapper/Mapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ public function __construct(
private string $name,
/** @var array<int, FieldMappingInterface> */
private array $fields = [],
private readonly ?CollectionOptionsInterface $collectionOptions = null)
{
private readonly ?MetadataMappingInterface $metadata = null,
private readonly ?CollectionOptionsInterface $collectionOptions = null,
) {
}

/**
Expand Down Expand Up @@ -45,4 +46,9 @@ public function getCollectionOptions(): ?CollectionOptionsInterface
{
return $this->collectionOptions;
}

public function getMetadata(): ?MetadataMappingInterface
{
return $this->metadata;
}
}
2 changes: 2 additions & 0 deletions BibliotecaTypesenseBundle/src/Mapper/MappingInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public function getFields(): array;
public function getName(): string;

public function getCollectionOptions(): ?CollectionOptionsInterface;

public function getMetadata(): ?MetadataMappingInterface;
}
52 changes: 52 additions & 0 deletions BibliotecaTypesenseBundle/src/Mapper/MetadataMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Biblioteca\TypesenseBundle\Mapper;

class MetadataMapping implements MetadataMappingInterface, \ArrayAccess, \IteratorAggregate, \Countable
{
public function __construct(private array $data)
{
}

public function getIterator(): \Traversable
{
return new \ArrayIterator($this->data);
}

public function offsetExists(mixed $offset): bool
{
return isset($this->data[$offset]);
}

public function offsetGet(mixed $offset): mixed
{
return $this->data[$offset] ?? null;
}

public function offsetSet(mixed $offset, mixed $value): void
{
if (is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}

public function offsetUnset(mixed $offset): void
{
unset($this->data[$offset]);
}

public function count(): int
{
return count($this->data);
}

/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return $this->data;
}
}
12 changes: 12 additions & 0 deletions BibliotecaTypesenseBundle/src/Mapper/MetadataMappingInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Biblioteca\TypesenseBundle\Mapper;

interface MetadataMappingInterface
{
/**
* Field options and value
* @return array<string,mixed>
*/
public function toArray(): array;
}
49 changes: 49 additions & 0 deletions BibliotecaTypesenseBundle/src/Populate/BatchGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Biblioteca\TypesenseBundle\Populate;

class BatchGenerator
{
private iterable $iterable;
private int $batchSize;

/**
* Constructor to initialize the iterable and batch size.
*
* @param iterable $iterable the data source to process
* @param int $batchSize the number of elements in each batch
* @throws \InvalidArgumentException if batch size is not greater than 0
*/
public function __construct(iterable $iterable, int $batchSize)
{
if ($batchSize <= 0) {
throw new \InvalidArgumentException('Batch size must be greater than 0.');
}

$this->iterable = $iterable;
$this->batchSize = $batchSize;
}

/**
* Generate batches of elements from the iterable.
*
* @return \Generator yields an array of elements in each batch
*/
public function generate(): \Generator
{
$batch = [];
foreach ($this->iterable as $item) {
$batch[] = $item;

if (count($batch) === $this->batchSize) {
yield $batch;
$batch = [];
}
}

// Yield remaining elements if they exist
if (!empty($batch)) {
yield $batch;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
<?php

namespace Biblioteca\TypesenseBundle;
namespace Biblioteca\TypesenseBundle\Populate;

use Biblioteca\TypesenseBundle\Client\ClientInterface;
use Biblioteca\TypesenseBundle\Mapper\FieldMappingInterface;
use Biblioteca\TypesenseBundle\Mapper\MapperInterface;
use Biblioteca\TypesenseBundle\Mapper\MappingInterface;
use Http\Client\Exception;
use Typesense\Collection;
use Typesense\Exceptions\TypesenseClientError;

class PopulateService
{
public function __construct(
private readonly ClientInterface $client,
private readonly int $batchSize = 100,
private readonly string $collectionPrefix = '',
) {
}
Expand All @@ -26,16 +29,21 @@ public function deleteCollection(MapperInterface $mapper): void
}
}

/**
* @throws Exception
* @throws TypesenseClientError
*/
public function createCollection(MapperInterface $mapper): Collection
{
$mapping = $mapper->getMapping();
$name = $this->getMappingName($mapping);

$payload = [
$payload = array_filter([
'name' => $name,
'fields' => array_map(fn (FieldMappingInterface $mapping) => $mapping->toArray(), $mapping->getFields()),
'metadata' => $mapping->getMetadata() ? $mapping->getMetadata()?->toArray() : null,
...$mapping->getCollectionOptions()?->toArray() ?? [],
];
], fn ($value) => !is_null($value));

$this->client->getCollections()->create($payload);

Expand All @@ -49,9 +57,9 @@ public function fillCollection(MapperInterface $mapper): \Generator

$collection = $this->client->getCollections()->offsetGet($name);
$data = $mapper->getData();
foreach ($data as $item) {
$collection->documents->create($item);
yield $item;
foreach (new BatchGenerator($data, $this->batchSize) as $items) {
$collection->documents->import($items);
yield from $items;
}
}

Expand Down
10 changes: 10 additions & 0 deletions BibliotecaTypesenseBundle/src/Query/InfixEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Biblioteca\TypesenseBundle\Query;

enum InfixEnum: string
{
case OFF = 'off';
case ALWAYS = 'always';
case FALLBACK = 'fallback';
}
116 changes: 116 additions & 0 deletions BibliotecaTypesenseBundle/src/Query/SearchParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Biblioteca\TypesenseBundle\Query;

class SearchParameter implements SearchParameterInterface
{
private readonly ?string $infix;
/**
* @var bool[]|null
*/
private readonly ?array $prefix;

/**
* @var string[]|null
*/
private readonly ?array $stopwords;

/**
* @param string|InfixEnum[]|null $infix
* @param bool|bool[]|null $prefix
* @param string[]|null $stopwords
*/
public function __construct(
private readonly string $q,
private readonly string $queryBy,
private readonly ?string $filterBy = null,
private readonly ?string $sortBy = null,
string|array|null $infix = null,
bool|array|null $prefix = null,
private readonly bool $preSegmentedQuery = false,
private readonly ?string $preset = null,
private readonly ?VectorQuery $vectorQuery = null,
?array $stopwords = null,
private readonly ?string $facetBy = null,
private readonly ?string $facetQuery = null,
private readonly ?int $remoteEmbeddingTimeoutMs = null,
private readonly ?int $remoteEmbeddingBatchSize = null,
private readonly ?int $remoteEmbeddingNumTries = null,
private readonly ?string $highlightFields = null,
private readonly ?int $highlightAffixNumTokens = null,
private readonly ?string $highlightStartTag = null,
private readonly ?string $highlightEndTag = null,
private readonly ?string $groupBy = null,
private readonly ?int $groupLimit = null,
private readonly ?int $numTypos = null,
private readonly ?int $page = null,
private readonly ?int $perPage = null,
private readonly ?int $maxFacetValues = null,
private readonly ?int $minLen1Typo = null,
private readonly ?int $minLen2Typo = null,
private readonly ?float $dropTokensThreshold = null,
private readonly ?array $hiddenHits = null,
private readonly ?string $excludeFields = null,
private readonly ?VoiceQueryInterface $voiceQuery = null,
) {
$this->infix = $this->convertArray($infix, fn ($infix) => $infix instanceof InfixEnum ? $infix->value : $infix, InfixEnum::class);
$this->prefix = $prefix === [] ? null : implode(',', array_map(fn (bool $value) => $value ? 'true' : 'false', $prefix));
$this->stopwords = $stopwords === [] ? null : implode(',', $stopwords);

// Check incompatible combinations
if ($this->vectorQuery !== null && $this->infix !== null) {
throw new \InvalidArgumentException('Cannot set both infix and vectorQuery');
}
}

private function convertArray(mixed $values, callable $convert, ?string $className = null): ?string
{
if (!is_array($values)) {
return $values === null ? null : $convert($values);
}
foreach ($values as $value) {
if ($className !== null && !$value instanceof $className) {
throw new \InvalidArgumentException(sprintf('Expected type %s, got %s', $className, is_object($value) ? get_class($value) : gettype($value)));
}
}

return $values === [] ? null : implode(',', array_map($convert, $values));
}

public function toArray(): array
{
return array_filter([
'q' => $this->q,
'query_by' => $this->queryBy,
'filter_by' => $this->filterBy,
'sort_by' => $this->sortBy,
'drop_tokens_threshold' => $this->dropTokensThreshold,
'facet_by' => $this->facetBy,
'facet_query' => $this->facetQuery,
'group_by' => $this->groupBy,
'group_limit' => $this->groupLimit,
'hidden_hits' => $this->hiddenHits,
'highlight_affix_num_tokens' => $this->highlightAffixNumTokens,
'highlight_end_tag' => $this->highlightEndTag,
'highlight_fields' => $this->highlightFields,
'highlight_start_tag' => $this->highlightStartTag,
'infix' => $this->infix,
'max_facet_values' => $this->maxFacetValues,
'min_len_1typo' => $this->minLen1Typo,
'min_len_2typo' => $this->minLen2Typo,
'num_typos' => $this->numTypos,
'page' => $this->page,
'per_page' => $this->perPage,
'pre_segmented_query' => $this->preSegmentedQuery,
'prefix' => $this->prefix,
'preset' => $this->preset,
'remote_embedding_batch_size' => $this->remoteEmbeddingBatchSize,
'remote_embedding_num_tries' => $this->remoteEmbeddingNumTries,
'remote_embedding_timeout_ms' => $this->remoteEmbeddingTimeoutMs,
'stopwords' => $this->stopwords,
'vector_query' => $this->vectorQuery,
'exclude_fields' => $this->excludeFields,
'voice_query' => $this->voiceQuery ? (string) $this->voiceQuery : null,
], fn (mixed $value) => !is_null($value));
}
}
11 changes: 11 additions & 0 deletions BibliotecaTypesenseBundle/src/Query/SearchParameterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Biblioteca\TypesenseBundle\Query;

interface SearchParameterInterface
{
/**
* @return array<string,mixed>
*/
public function toArray(): array;
}
Loading

0 comments on commit 93f8906

Please sign in to comment.