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

Restore DB filters #45

Closed
wants to merge 1 commit into from
Closed
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
37 changes: 25 additions & 12 deletions src/AbstractQueryDataReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
* @psalm-var array<TKey, TValue>|null
*/
private ?array $data = null;

/**
* @psalm-var non-negative-int|null
*/
private ?int $batchSize = 100;
private ?string $countParam = null;

Expand All @@ -62,9 +66,7 @@
*/
public function getIterator(): Generator
{
if (is_array($this->data)) {
yield from $this->data;
} elseif ($this->batchSize === null) {
if ($this->batchSize === null) {
yield from $this->read();
} else {
$iterator = $this->getPreparedQuery()->each($this->batchSize);
Expand All @@ -86,16 +88,16 @@
if ($this->count === null) {
$q = $this->countParam ?? '*';

if ($q === '*' && is_array($this->data) && !$this->limit && !$this->offset) {

Check warning on line 91 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ { if ($this->count === null) { $q = $this->countParam ?? '*'; - if ($q === '*' && is_array($this->data) && !$this->limit && !$this->offset) { + if ($q !== '*' && is_array($this->data) && !$this->limit && !$this->offset) { $this->count = count($this->data); } else { $query = $this->getPreparedQuery();

Check warning on line 91 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "LogicalNot": --- Original +++ New @@ @@ { if ($this->count === null) { $q = $this->countParam ?? '*'; - if ($q === '*' && is_array($this->data) && !$this->limit && !$this->offset) { + if ($q === '*' && is_array($this->data) && $this->limit && !$this->offset) { $this->count = count($this->data); } else { $query = $this->getPreparedQuery();

Check warning on line 91 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "LogicalNot": --- Original +++ New @@ @@ { if ($this->count === null) { $q = $this->countParam ?? '*'; - if ($q === '*' && is_array($this->data) && !$this->limit && !$this->offset) { + if ($q === '*' && is_array($this->data) && !$this->limit && $this->offset) { $this->count = count($this->data); } else { $query = $this->getPreparedQuery();
$this->count = count($this->data);
} else {
$query = $this->getPreparedQuery();
$query->offset(null);

Check warning on line 95 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ $this->count = count($this->data); } else { $query = $this->getPreparedQuery(); - $query->offset(null); + $query->limit(null); $query->orderBy(''); /** @psalm-var non-negative-int */
$query->limit(null);

Check warning on line 96 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ } else { $query = $this->getPreparedQuery(); $query->offset(null); - $query->limit(null); + $query->orderBy(''); /** @psalm-var non-negative-int */ $this->count = (int) $query->count($q);
$query->orderBy('');

Check warning on line 97 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ $query = $this->getPreparedQuery(); $query->offset(null); $query->limit(null); - $query->orderBy(''); + /** @psalm-var non-negative-int */ $this->count = (int) $query->count($q); }

/** @psalm-var non-negative-int */
$this->count = (int) $query->count($q);

Check warning on line 100 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "CastInt": --- Original +++ New @@ @@ $query->limit(null); $query->orderBy(''); /** @psalm-var non-negative-int */ - $this->count = (int) $query->count($q); + $this->count = $query->count($q); } } return $this->count;
}
}

Expand All @@ -104,15 +106,16 @@

public function getPreparedQuery(): QueryInterface
{
$query = $this->applyFilter(clone $this->query);
$query = clone $this->query;

Check warning on line 109 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "CloneRemoval": --- Original +++ New @@ @@ } public function getPreparedQuery() : QueryInterface { - $query = clone $this->query; + $query = $this->query; $query = $this->applyFilter($query); $query = $this->applyHaving($query); if ($this->limit) {
$query = $this->applyFilter($query);
$query = $this->applyHaving($query);

if ($this->limit) {
$query->limit($this->limit);

Check warning on line 114 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ $query = $this->applyFilter($query); $query = $this->applyHaving($query); if ($this->limit) { - $query->limit($this->limit); + } if ($this->offset) { $query->offset($this->offset);
}

if ($this->offset) {
$query->offset($this->offset);

Check warning on line 118 in src/AbstractQueryDataReader.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ $query->limit($this->limit); } if ($this->offset) { - $query->offset($this->offset); + } if ($criteria = $this->sort?->getCriteria()) { $query->addOrderBy($criteria);
}

if ($criteria = $this->sort?->getCriteria()) {
Expand All @@ -125,10 +128,9 @@
protected function applyFilter(QueryInterface $query): QueryInterface
{
if ($this->filter !== null) {
$condition = $this->criteriaHandler->handle($this->filter->toCriteriaArray());
if ($condition !== null) {
$query = $query->andWhere($condition);
}
return $this->criteriaHandler
->getHandlerByOperator($this->filter)
->applyFilter($this->query, $this->filter, $this->criteriaHandler);
}

return $query;
Expand All @@ -137,10 +139,9 @@
protected function applyHaving(QueryInterface $query): QueryInterface
{
if ($this->having !== null) {
$condition = $this->criteriaHandler->handle($this->having->toCriteriaArray());
if ($condition !== null) {
$query = $query->andHaving($condition);
}
return $this->criteriaHandler
->getHandlerByOperator($this->having)
->applyHaving($this->query, $this->having, $this->criteriaHandler);
}

return $query;
Expand All @@ -152,6 +153,10 @@
*/
public function withOffset(int $offset): static
{
if ($this->offset === $offset) {
return $this;
}

$new = clone $this;
$new->data = null;
$new->offset = $offset;
Expand All @@ -169,6 +174,10 @@
throw new InvalidArgumentException('$limit must not be less than 0.');
}

if ($this->limit === $limit) {
return $this;
}

$new = clone $this;
$new->data = null;
$new->limit = $limit;
Expand Down Expand Up @@ -242,6 +251,10 @@
throw new InvalidArgumentException('$batchSize cannot be less than 1.');
}

if ($this->batchSize === $batchSize) {
return $this;
}

$new = clone $this;
$new->batchSize = $batchSize;

Expand Down
68 changes: 29 additions & 39 deletions src/CriteriaHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,45 @@
use Yiisoft\Data\Db\FilterHandler\AllHandler;
use Yiisoft\Data\Db\FilterHandler\AnyHandler;
use Yiisoft\Data\Db\FilterHandler\BetweenHandler;
use Yiisoft\Data\Db\FilterHandler\Context;
use Yiisoft\Data\Db\FilterHandler\EqualsEmptyHandler;
use Yiisoft\Data\Db\FilterHandler\EqualsHandler;
use Yiisoft\Data\Db\FilterHandler\EqualsNullHandler;
use Yiisoft\Data\Db\FilterHandler\ExistsHandler;
use Yiisoft\Data\Db\FilterHandler\GreaterThanHandler;
use Yiisoft\Data\Db\FilterHandler\GreaterThanOrEqualHandler;
use Yiisoft\Data\Db\FilterHandler\ILikeHandler;
use Yiisoft\Data\Db\FilterHandler\InHandler;
use Yiisoft\Data\Db\FilterHandler\LessThanHandler;
use Yiisoft\Data\Db\FilterHandler\LessThanOrEqualHandler;
use Yiisoft\Data\Db\FilterHandler\LikeHandler;
use Yiisoft\Data\Db\FilterHandler\NotHandler;
use Yiisoft\Data\Db\FilterHandler\OrILikeHandler;
use Yiisoft\Data\Db\FilterHandler\OrLikeHandler;
use Yiisoft\Data\Db\FilterHandler\QueryHandlerInterface;
use Yiisoft\Data\Reader\FilterHandlerInterface;
use Yiisoft\Data\Reader\FilterInterface;
use Yiisoft\Db\Query\QueryPartsInterface;

use function array_merge;
use function array_unshift;

/**
* `CriteriaHandler` processes filter criteria array from {@see FilterInterface::toCriteriaArray()} into condition array
* that is used in {@see QueryPartsInterface::andWhere()} and {@see QueryPartsInterface::andHaving()}.
*/
final class CriteriaHandler
{
private Context $context;

/**
* @psalm-var array<string, QueryHandlerInterface>
*/
private array $handlers;

/**
* @param QueryHandlerInterface[]|null $handlers
* @param ValueNormalizerInterface|null $valueNormalizer
* @param QueryHandlerInterface ...$handlers
*/
public function __construct(
?array $handlers = null,
ValueNormalizerInterface $valueNormalizer = null,
QueryHandlerInterface ...$handlers
) {
if (empty($handlers)) {
if ($handlers === []) {
$handlers = [
new AllHandler(),
new AnyHandler(),
Expand All @@ -55,6 +56,9 @@ public function __construct(
new LessThanHandler(),
new LessThanOrEqualHandler(),
new LikeHandler(),
new ILikeHandler(),
new OrLikeHandler(),
new OrILikeHandler(),
new InHandler(),
new ExistsHandler(),
new NotHandler(),
Expand All @@ -64,64 +68,50 @@ public function __construct(
];
}

$this->handlers = $this->prepareHandlers($handlers);
$this->context = new Context($this, $valueNormalizer ?? new ValueNormalizer());
$this->handlers = $this->prepareHandlers(...$handlers);
}

public function withFilterHandlers(FilterHandlerInterface ...$handlers): self
public function withFilterHandlers(QueryHandlerInterface $handler, QueryHandlerInterface ...$handlers): self
{
foreach ($handlers as $handler) {
if (!$handler instanceof QueryHandlerInterface) {
throw new LogicException(
sprintf(
'Filter handler must implement "%s".',
QueryHandlerInterface::class,
)
);
}
}
/** @var QueryHandlerInterface[] $handlers */
array_unshift($handlers, $handler);

$new = clone $this;
$new->handlers = array_merge(
$this->handlers,
$this->prepareHandlers($handlers),
$this->prepareHandlers(...$handlers),
);
return $new;
}

public function handle(array $criteria): ?array
public function hasHandler(string|FilterInterface $operator): bool
{
if (!isset($criteria[0])) {
throw new LogicException('Incorrect criteria array.');
if ($operator instanceof FilterInterface) {
$operator = $operator::getOperator();
}

$operator = $criteria[0];
if (!is_string($operator)) {
throw new LogicException('Criteria operator must be a string.');
}

$operands = array_slice($criteria, 1);

return $this->getHandlerByOperator($operator)->getCondition($operands, $this->context);
return isset($this->handlers[$operator]);
}

private function getHandlerByOperator(string $operator): QueryHandlerInterface
public function getHandlerByOperator(string|FilterInterface $operator): QueryHandlerInterface
{
if (!isset($this->handlers[$operator])) {
if ($operator instanceof FilterInterface) {
$operator = $operator::getOperator();
}

if (!$this->hasHandler($operator)) {
throw new LogicException(sprintf('Operator "%s" is not supported', $operator));
}

return $this->handlers[$operator];
}

/**
* @param QueryHandlerInterface[] $handlers
* @param QueryHandlerInterface ...$handlers
*
* @return QueryHandlerInterface[]
* @psalm-return array<string, QueryHandlerInterface>
*/
private function prepareHandlers(array $handlers): array
private function prepareHandlers(QueryHandlerInterface ...$handlers): array
{
$result = [];
foreach ($handlers as $handler) {
Expand Down
15 changes: 15 additions & 0 deletions src/Filter/All.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Data\Reader\Filter\All as FilterAll;

final class All extends GroupFilter
{
public static function getOperator(): string
{
return FilterAll::getOperator();
}
}
15 changes: 15 additions & 0 deletions src/Filter/Any.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Data\Reader\Filter\Any as FilterAny;

final class Any extends GroupFilter
{
public static function getOperator(): string
{
return FilterAny::getOperator();
}
}
57 changes: 57 additions & 0 deletions src/Filter/Between.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Data\Reader\Filter\Between as BetweenFilter;
use Yiisoft\Db\Expression\ExpressionInterface;

final class Between implements QueryFilterInterface
{
use ParamsTrait;

public function __construct(
private readonly string|ExpressionInterface $column,
private readonly mixed $min,
private readonly mixed $max,
array $params = []
) {
$this->params = $params;
}

public static function getOperator(): string
{
return BetweenFilter::getOperator();
}

private static function isEmpty(mixed $value): bool
{
return $value === null || $value === '';
}

public function toCriteriaArray(): array
{
$isMinEmpty = self::isEmpty($this->min);
$isMaxEmpty = self::isEmpty($this->max);

if (!$isMinEmpty && !$isMaxEmpty) {
return [
self::getOperator(),
$this->column,
$this->min,
$this->max,
];
}

if (!$isMinEmpty) {
return (new GreaterThanOrEqual($this->column, $this->min))->toCriteriaArray();
}

if (!$isMaxEmpty) {
return (new LessThanOrEqual($this->column, $this->max))->toCriteriaArray();
}

return [];
}
}
48 changes: 48 additions & 0 deletions src/Filter/CompareFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Data\Db\Filter;

use Yiisoft\Db\Expression\ExpressionInterface;

abstract class CompareFilter implements QueryFilterInterface
{
use ParamsTrait;

protected bool $ignoreNull = false;

/**
* @param ExpressionInterface|string $column
* @param mixed $value
* @param array $params
*/
public function __construct(
protected readonly string|ExpressionInterface $column,
protected mixed $value,
array $params = []
) {
$this->params = $params;
}

public function withIgnoreNull(bool $ignoreNull = true): static
{
if ($this->ignoreNull === $ignoreNull) {
return $this;
}

$new = clone $this;
$new->ignoreNull = $ignoreNull;

return $new;
}

public function toCriteriaArray(): array
{
if ($this->value === null) {
return $this->ignoreNull ? [] : (new EqualsNull($this->column))->toCriteriaArray();
}

return [static::getOperator(), $this->column , $this->value];
}
}
Loading
Loading