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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"nette/tester": "^2.3.4",
"nextras/dbal": "^4.0 || ^5.0",
"nextras/orm": "^4.0 || ^5.0",
"opensearch-project/opensearch-php": "^2.6",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-mockery": "^2.0",
Expand Down
210 changes: 210 additions & 0 deletions src/DataSource/OpenSearchDataSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<?php declare(strict_types = 1);

namespace Contributte\Datagrid\DataSource;

use Contributte\Datagrid\Exception\DatagridDateTimeHelperException;
use Contributte\Datagrid\Filter\FilterDate;
use Contributte\Datagrid\Filter\FilterDateRange;
use Contributte\Datagrid\Filter\FilterMultiSelect;
use Contributte\Datagrid\Filter\FilterRange;
use Contributte\Datagrid\Filter\FilterSelect;
use Contributte\Datagrid\Filter\FilterText;
use Contributte\Datagrid\Utils\DateTimeHelper;
use Contributte\Datagrid\Utils\Sorting;
use OpenSearch\Client;
use RuntimeException;
use UnexpectedValueException;

class OpenSearchDataSource extends FilterableDataSource implements IDataSource
{

protected SearchParamsBuilder $searchParamsBuilder;

/** @var callable */
private $rowFactory;

public function __construct(private Client $client, string $indexName, ?callable $rowFactory = null)
{
$this->searchParamsBuilder = new SearchParamsBuilder($indexName, true);

if ($rowFactory === null) {
$rowFactory = static fn (array $hit): array => $hit['_source'];
}

$this->rowFactory = $rowFactory;
}

public function getCount(): int
{
$searchResult = $this->client->search($this->searchParamsBuilder->buildParams());

if (!isset($searchResult['hits'])) {
throw new UnexpectedValueException();
}

$count = $this->client->count($this->searchParamsBuilder->buildParams());

return $count['count'];
}

/**
* {@inheritDoc}
*/
public function getData(): array
{
$searchResult = $this->client->search($this->searchParamsBuilder->buildParams());

if (!isset($searchResult['hits'])) {
throw new UnexpectedValueException();
}

return array_map($this->rowFactory, $searchResult['hits']['hits']);
}

/**
* {@inheritDoc}
*/
public function filterOne(array $condition): IDataSource
{
foreach ($condition as $value) {
$this->searchParamsBuilder->addIdsQuery($value);
}

return $this;
}

public function limit(int $offset, int $limit): IDataSource
{
$this->searchParamsBuilder->setFrom($offset);
$this->searchParamsBuilder->setSize($limit);

return $this;
}

public function applyFilterDate(FilterDate $filter): void
{
foreach ($filter->getCondition() as $column => $value) {
$timestampFrom = null;
$timestampTo = null;

if ($value) {
try {
$dateFrom = DateTimeHelper::tryConvertToDateTime($value, [$filter->getPhpFormat()]);
$dateFrom->setTime(0, 0, 0);

$timestampFrom = $dateFrom->getTimestamp();

$dateTo = DateTimeHelper::tryConvertToDateTime($value, [$filter->getPhpFormat()]);
$dateTo->setTime(23, 59, 59);

$timestampTo = $dateTo->getTimestamp();

$this->searchParamsBuilder->addRangeQuery($column, $timestampFrom, $timestampTo);
} catch (DatagridDateTimeHelperException) {
// ignore the invalid filter value
}
}
}
}

public function applyFilterDateRange(FilterDateRange $filter): void
{
foreach ($filter->getCondition() as $column => $values) {
$timestampFrom = null;
$timestampTo = null;

if ($values['from']) {
try {
$dateFrom = DateTimeHelper::tryConvertToDateTime($values['from'], [$filter->getPhpFormat()]);
$dateFrom->setTime(0, 0, 0);

$timestampFrom = $dateFrom->getTimestamp();
} catch (DatagridDateTimeHelperException) {
// ignore the invalid filter value
}
}

if ($values['to']) {
try {
$dateTo = DateTimeHelper::tryConvertToDateTime($values['to'], [$filter->getPhpFormat()]);
$dateTo->setTime(23, 59, 59);

$timestampTo = $dateTo->getTimestamp();
} catch (DatagridDateTimeHelperException) {
// ignore the invalid filter value
}
}

if (is_int($timestampFrom) || is_int($timestampTo)) {
$this->searchParamsBuilder->addRangeQuery($column, $timestampFrom, $timestampTo);
}
}
}

public function applyFilterRange(FilterRange $filter): void
{
foreach ($filter->getCondition() as $column => $value) {
$this->searchParamsBuilder->addRangeQuery($column, $value['from'] ?? null, $value['to'] ?? null);
}
}

public function applyFilterText(FilterText $filter): void
{
foreach ($filter->getCondition() as $column => $value) {
$options = [];
if ($filter->isCaseInsensitive()) {
$options['case_insensitive'] = true;
}

if ($filter->isExactSearch()) {
$this->searchParamsBuilder->addMatchQuery($column, $value);
} elseif ($filter->isWildCardSearch()) {
$this->searchParamsBuilder->addWildCardQuery($column, $value, $options);
} elseif ($filter->isTermSearch()) {
$this->searchParamsBuilder->addTermQuery($column, $value, $options);
} else {
$this->searchParamsBuilder->addPhrasePrefixQuery($column, $value);
}
}
}

public function applyFilterMultiSelect(FilterMultiSelect $filter): void
{
foreach ($filter->getCondition() as $column => $values) {
$this->searchParamsBuilder->addBooleanMatchQuery($column, $values);
}
}

public function applyFilterSelect(FilterSelect $filter): void
{
foreach ($filter->getCondition() as $column => $value) {
$this->searchParamsBuilder->addMatchQuery($column, $value);
}
}

/**
* {@inheritDoc}
*
* @throws RuntimeException
*/
public function sort(Sorting $sorting): IDataSource
{
if (is_callable($sorting->getSortCallback())) {
throw new RuntimeException('No can do - not implemented yet');
}

foreach ($sorting->getSort() as $column => $order) {
$this->searchParamsBuilder->setSort(
[$column => ['order' => strtolower($order)]]
);
}

return $this;
}

public function getDataSource(): SearchParamsBuilder
{
return $this->searchParamsBuilder;
}

}
62 changes: 57 additions & 5 deletions src/DataSource/SearchParamsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ final class SearchParamsBuilder

private array $phrasePrefixQueries = [];

private array $wildCardQueries = [];

private array $matchQueries = [];

private array $booleanMatchQueries = [];
Expand All @@ -21,7 +23,9 @@ final class SearchParamsBuilder

private array $idsQueries = [];

public function __construct(private string $indexName)
private array $termQueries = [];

public function __construct(private string $indexName, private bool $isOpensearch = false)
{
}

Expand Down Expand Up @@ -50,6 +54,16 @@ public function addIdsQuery(array $ids): void
$this->idsQueries[] = $ids;
}

public function addWildCardQuery(string $field, string $query, array $options = []): void
{
$this->wildCardQueries[] = [$field => [$query, $options]];
}

public function addTermQuery(string $field, string $query, array $options = []): void
{
$this->termQueries[] = [$field => [$query, $options]];
}

public function setSort(array $sort): void
{
$this->sort = $sort;
Expand Down Expand Up @@ -91,7 +105,9 @@ public function buildParams(): array
&& $this->matchQueries === []
&& $this->booleanMatchQueries === []
&& $this->rangeQueries === []
&& $this->idsQueries === []) {
&& $this->idsQueries === []
&& $this->wildCardQueries === []
&& $this->termQueries === []) {
return $return;
}

Expand All @@ -117,9 +133,45 @@ public function buildParams(): array
foreach ($matchQuery as $field => $query) {
$return['body']['query']['bool']['must'][] = [
'match' => [
$field => [
'query' => $query,
],
$field => ['query' => $query],
],
];
}
}

foreach ($this->termQueries as $matchQuery) {
foreach ($matchQuery as $field => [$query, $options]) {
$fieldQueryParams = [
'value' => $query,
];
if ($this->isOpensearch && count($options) > 0) {
$fieldQueryParams = array_merge($fieldQueryParams, $options);
}

$return['body']['query']['bool']['must'][] = [
'term' => [
$field => $fieldQueryParams,
],
];
}
}

foreach ($this->wildCardQueries as $wildCardQuery) {
foreach ($wildCardQuery as $field => [$query, $options]) {
if (!(str_contains($query, '*') || str_contains($query, '?'))) {
$query .= '*';
}

$fieldQueryParams = [
'value' => $query,
];
if (count($options) > 0) {
$fieldQueryParams = array_merge($fieldQueryParams, $options);
}

$return['body']['query']['bool']['must'][] = [
'wildcard' => [
$field => $fieldQueryParams,
],
];
}
Expand Down
42 changes: 42 additions & 0 deletions src/Filter/FilterText.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ class FilterText extends Filter

protected bool $exact = false;

protected bool $wildCard = false;

protected bool $caseInsensitive = false;

protected bool $term = false;

protected bool $splitWordsSearch = true;

protected bool $conjunctionSearch = false;
Expand Down Expand Up @@ -96,4 +102,40 @@ public function hasConjunctionSearch(): bool
return $this->conjunctionSearch;
}

public function setWildCard(bool $wildCard = true): self
{
$this->wildCard = $wildCard;

return $this;
}

public function isWildCardSearch(): bool
{
return $this->wildCard;
}

public function setCaseInsensitive(bool $caseInsensitive = true): self
{
$this->caseInsensitive = $caseInsensitive;

return $this;
}

public function isCaseInsensitive(): bool
{
return $this->caseInsensitive;
}

public function setTermSearch(bool $term = true): self
{
$this->term = $term;

return $this;
}

public function isTermSearch(): bool
{
return $this->term;
}

}
Loading
Loading