Skip to content

Commit

Permalink
Merge pull request #531 from nextras/expression_rewrite
Browse files Browse the repository at this point in the history
Dbal expression rewrite
  • Loading branch information
hrach authored Aug 14, 2021
2 parents 14e7ace + 03be1fa commit 57d7de9
Show file tree
Hide file tree
Showing 35 changed files with 672 additions and 157 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"nette/caching": "~2.5 || ~3.0",
"nette/utils": "~2.5 || ~3.0",
"nette/tokenizer": "~2.3 || ~3.0",
"nextras/dbal": "~4.0"
"nextras/dbal": "~5.0@dev"
},
"require-dev": {
"nette/bootstrap": "~2.4 || ~3.0",
Expand Down
2 changes: 1 addition & 1 deletion src/Collection/ArrayCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public function getByIdChecked($id): IEntity
public function findBy(array $conds): ICollection
{
$collection = clone $this;
$collection->collectionFilter[] = $this->getHelper()->createFilter($conds);
$collection->collectionFilter[] = $this->getHelper()->createFilter($conds, null);
return $collection;
}

Expand Down
24 changes: 17 additions & 7 deletions src/Collection/DbalCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@ public function getByIdChecked($id): IEntity
public function findBy(array $conds): ICollection
{
$collection = clone $this;
$expression = $collection->getHelper()->processFilterFunction($collection->queryBuilder, $conds);
$expression = $collection->getHelper()->processFilterFunction($collection->queryBuilder, $conds, null);
$expression = $expression->applyAggregator($collection->queryBuilder);

foreach ($expression->getUniqueJoins($collection->queryBuilder) as $join) {
$join->applyJoin($collection->queryBuilder);
}

if ($expression->isHavingClause) {
$collection->queryBuilder->andHaving(...$expression->args);
} else {
Expand All @@ -135,16 +141,20 @@ public function orderBy($expression, string $direction = ICollection::ASC): ICol
$expression = $expression; // no-op for PHPStan

foreach ($expression as $subExpression => $subDirection) {
$orderArgs = $collection->getHelper()
->processOrder($collection->queryBuilder, $subExpression, $subDirection);
$collection->queryBuilder->addOrderBy('%ex', $orderArgs);
$collection->getHelper()->processOrder(
$collection->queryBuilder,
$subExpression,
$subDirection
);
}
} else {
/** @phpstan-var string|list<mixed> $expression */
$expression = $expression; // no-op for PHPStan

$orderArgs = $collection->getHelper()->processOrder($collection->queryBuilder, $expression, $direction);
$collection->queryBuilder->addOrderBy('%ex', $orderArgs);
$collection->getHelper()->processOrder(
$collection->queryBuilder,
$expression,
$direction
);
}
return $collection;
}
Expand Down
48 changes: 48 additions & 0 deletions src/Collection/Functions/AnyAggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Functions;


use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\ArrayAnyAggregator;
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\DbalAnyAggregator;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Collection\Helpers\IArrayAggregator;
use Nextras\Orm\Collection\Helpers\IDbalAggregator;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Exception\InvalidStateException;


class AnyAggregateFunction implements IArrayFunction, IQueryBuilderFunction
{
public function processArrayExpression(
ArrayCollectionHelper $helper,
IEntity $entity,
array $args,
?IArrayAggregator $aggregator = null
)
{
if ($aggregator !== null) {
throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
}

return $helper->getValue($entity, $args[0], new ArrayAnyAggregator())->value;
}


public function processQueryBuilderExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
?IDbalAggregator $aggregator = null
): DbalExpressionResult
{
if ($aggregator !== null) {
throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
}

return $helper->processPropertyExpr($builder, $args[0], new DbalAnyAggregator());
}
}
47 changes: 39 additions & 8 deletions src/Collection/Functions/BaseAggregateFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Collection\Helpers\IArrayAggregator;
use Nextras\Orm\Collection\Helpers\IDbalAggregator;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Exception\InvalidArgumentException;
use Nextras\Orm\Exception\InvalidStateException;
use function assert;
use function count;
use function is_array;
Expand All @@ -34,11 +37,16 @@ protected function __construct(string $sqlFunction)
abstract protected function calculateAggregation(array $values);


public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $entity, array $args)
public function processArrayExpression(
ArrayCollectionHelper $helper,
IEntity $entity,
array $args,
?IArrayAggregator $aggregator = null
)
{
assert(count($args) === 1 && is_string($args[0]));

$valueReference = $helper->getValue($entity, $args[0]);
$valueReference = $helper->getValue($entity, $args[0], $aggregator);
if (!$valueReference->isMultiValue) {
throw new InvalidArgumentException('Aggregation has to be called over has many relationship.');
}
Expand All @@ -51,15 +59,38 @@ public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $e
public function processQueryBuilderExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args
array $args,
?IDbalAggregator $aggregator = null
): DbalExpressionResult
{
assert(count($args) === 1 && is_string($args[0]));

$expression = $helper->processPropertyExpr($builder, $args[0]);
return new DbalExpressionResult(
["{$this->sqlFunction}(%ex)", $expression->args],
true
);
if ($aggregator !== null) {
throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
}

$aggregator = new class implements IDbalAggregator {
/** @var string */
public $sqlFunction;


public function aggregate(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression
): DbalExpressionResult
{
return new DbalExpressionResult(
["{$this->sqlFunction}(%ex)", $expression->args],
$expression->joins,
null,
true,
null,
null
);
}
};
$aggregator->sqlFunction = $this->sqlFunction;

return $helper->processPropertyExpr($builder, $args[0], $aggregator)->applyAggregator($builder);
}
}
53 changes: 30 additions & 23 deletions src/Collection/Functions/BaseCompareFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,44 @@


use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\ArrayAnyAggregator;
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Collection\Helpers\IArrayAggregator;
use Nextras\Orm\Collection\Helpers\IDbalAggregator;
use Nextras\Orm\Entity\IEntity;
use function assert;
use function count;


abstract class BaseCompareFunction implements IArrayFunction, IQueryBuilderFunction
{
public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $entity, array $args)
public function processArrayExpression(
ArrayCollectionHelper $helper,
IEntity $entity,
array $args,
?IArrayAggregator $aggregator = null
)
{
assert(count($args) === 2);

$valueReference = $helper->getValue($entity, $args[0]);
$valueReference = $helper->getValue($entity, $args[0], $aggregator);
if ($valueReference->propertyMetadata !== null) {
$targetValue = $helper->normalizeValue($args[1], $valueReference->propertyMetadata, true);
} else {
$targetValue = $args[1];
}

if ($valueReference->isMultiValue) {
foreach ($valueReference->value as $subValue) {
if ($this->evaluateInPhp($subValue, $targetValue)) {
return true;
}
}
return false;
$values = array_map(
function ($value) use ($targetValue): bool {
return $this->evaluateInPhp($value, $targetValue);
},
$valueReference->value
);
$aggregator = $valueReference->aggregator ?? new ArrayAnyAggregator();
return $aggregator->aggregate($values);
} else {
return $this->evaluateInPhp($valueReference->value, $targetValue);
}
Expand All @@ -41,25 +51,22 @@ public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $e
public function processQueryBuilderExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args
array $args,
?IDbalAggregator $aggregator = null
): DbalExpressionResult
{
assert(count($args) === 2);

return $helper->processPropertyExpr(
$builder,
$args[0],
function (DbalExpressionResult $expression) use ($args): DbalExpressionResult {
if ($expression->valueNormalizer !== null) {
$cb = $expression->valueNormalizer;
$value = $cb($args[1]);
} else {
$value = $args[1];
}

return $this->evaluateInDb($expression, $value);
}
);
$expression = $helper->processPropertyExpr($builder, $args[0], $aggregator);

if ($expression->valueNormalizer !== null) {
$cb = $expression->valueNormalizer;
$value = $cb($args[1]);
} else {
$value = $args[1];
}

return $this->evaluateInDb($expression, $value);
}


Expand Down
4 changes: 2 additions & 2 deletions src/Collection/Functions/CompareEqualsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ protected function evaluateInDb(DbalExpressionResult $expression, $value): DbalE
}
return $combined;
}, $value);
return new DbalExpressionResult(['%multiOr', $value], $expression->isHavingClause);
return $expression->withArgs(['%multiOr', $value]);
} else {
return $expression->append('IN %any', $value);
}
} else {
return new DbalExpressionResult(['1=0'], $expression->isHavingClause);
return $expression->withArgs(['1=0']);
}
} elseif ($value === null) {
return $expression->append('IS NULL');
Expand Down
16 changes: 12 additions & 4 deletions src/Collection/Functions/CompareLikeFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Collection\Helpers\IArrayAggregator;
use Nextras\Orm\Collection\Helpers\IDbalAggregator;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Exception\InvalidStateException;
use function preg_quote;
Expand All @@ -17,11 +19,16 @@

class CompareLikeFunction implements IArrayFunction, IQueryBuilderFunction
{
public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $entity, array $args)
public function processArrayExpression(
ArrayCollectionHelper $helper,
IEntity $entity,
array $args,
?IArrayAggregator $aggregator = null
)
{
assert(count($args) === 2);

$valueReference = $helper->getValue($entity, $args[0]);
$valueReference = $helper->getValue($entity, $args[0], $aggregator);

$likeExpression = $args[1];
assert($likeExpression instanceof LikeExpression);
Expand Down Expand Up @@ -49,12 +56,13 @@ public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $e
public function processQueryBuilderExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args
array $args,
?IDbalAggregator $aggregator = null
): DbalExpressionResult
{
assert(count($args) === 2);

$expression = $helper->processPropertyExpr($builder, $args[0]);
$expression = $helper->processPropertyExpr($builder, $args[0], $aggregator);

$likeExpression = $args[1];
assert($likeExpression instanceof LikeExpression);
Expand Down
4 changes: 2 additions & 2 deletions src/Collection/Functions/CompareNotEqualsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ protected function evaluateInDb(DbalExpressionResult $expression, $value): DbalE
}
return $combined;
}, $value);
return new DbalExpressionResult(['NOT (%multiOr)', $value], $expression->isHavingClause);
return $expression->withArgs(['NOT (%multiOr)', $value]);
} else {
return $expression->append('NOT IN %any', $value);
}
} else {
return new DbalExpressionResult(['1=1'], $expression->isHavingClause);
return $expression->withArgs(['1=1']);
}
} elseif ($value === null) {
return $expression->append('IS NOT NULL');
Expand Down
Loading

0 comments on commit 57d7de9

Please sign in to comment.