Skip to content

Commit

Permalink
Merge pull request #545 from nextras/joins-part1
Browse files Browse the repository at this point in the history
Joins refactoring - part 1
  • Loading branch information
hrach authored Dec 7, 2021
2 parents b19179f + f76838a commit 81c1775
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 186 deletions.
85 changes: 53 additions & 32 deletions src/Collection/DbalCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Iterator;
use Nextras\Dbal\IConnection;
use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Collection\Helpers\FetchPairsHelper;
use Nextras\Orm\Entity\IEntity;
Expand Down Expand Up @@ -53,6 +54,12 @@ class DbalCollection implements ICollection
/** @var QueryBuilder */
protected $queryBuilder;

/** @var array<mixed> FindBy expressions for deferred processing */
protected $filtering = [];

/** @var array<array{DbalExpressionResult, string}> OrderBy expression result & sorting direction */
protected $ordering = [];

/** @var DbalQueryBuilderHelper */
protected $helper;

Expand Down Expand Up @@ -117,44 +124,32 @@ public function getByIdChecked($id): IEntity
public function findBy(array $conds): ICollection
{
$collection = clone $this;
$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->expression, ...$expression->args);
} else {
$collection->queryBuilder->andWhere($expression->expression, ...$expression->args);
}
$collection->filtering[] = $conds;
return $collection;
}


public function orderBy($expression, string $direction = ICollection::ASC): ICollection
{
$collection = clone $this;
$helper = $collection->getHelper();
if (is_array($expression) && !isset($expression[0])) {
/** @phpstan-var array<string, string> $expression */
$expression = $expression; // no-op for PHPStan

foreach ($expression as $subExpression => $subDirection) {
$collection->getHelper()->processOrder(
$collection->queryBuilder,
$subExpression,
$subDirection
);
$collection->ordering[] = [
$helper->processPropertyExpr($collection->queryBuilder, $subExpression),
$subDirection,
];
}
} else {
/** @phpstan-var string|list<mixed> $expression */
$expression = $expression; // no-op for PHPStan
$collection->getHelper()->processOrder(
$collection->queryBuilder,
$expression,
$direction
);
$collection->ordering[] = [
$helper->processPropertyExpr($collection->queryBuilder, $expression),
$direction,
];
}
return $collection;
}
Expand All @@ -163,7 +158,7 @@ public function orderBy($expression, string $direction = ICollection::ASC): ICol
public function resetOrderBy(): ICollection
{
$collection = clone $this;
$collection->queryBuilder->orderBy(null);
$collection->getQueryBuilder()->orderBy(null);
return $collection;
}

Expand Down Expand Up @@ -313,19 +308,50 @@ public function __clone()


/**
* @return QueryBuilder
* @internal
*/
public function getQueryBuilder()
public function getQueryBuilder(): QueryBuilder
{
$joins = [];
$helper = $this->getHelper();
$args = $this->filtering;

if (count($args) > 0) {
array_unshift($args, ICollection::AND);
$expression = $helper->processFilterFunction(
$this->queryBuilder,
$args,
null
);
$joins = $expression->joins;
if ($expression->isHavingClause) {
$this->queryBuilder->andHaving($expression->expression, ...$expression->args);
} else {
$this->queryBuilder->andWhere($expression->expression, ...$expression->args);
}
$this->filtering = [];
}

foreach ($this->ordering as [$expression, $direction]) {
$joins = array_merge($joins, $expression->joins);
$orderingExpression = $helper->processOrderDirection($expression, $direction);
$this->queryBuilder->addOrderBy('%ex', $orderingExpression);
}
$this->ordering = [];

$mergedJoins = $helper->mergeJoins('%and', $joins);
foreach ($mergedJoins as $join) {
$join->applyJoin($this->queryBuilder);
}

return $this->queryBuilder;
}


protected function getIteratorCount(): int
{
if ($this->resultCount === null) {
$builder = clone $this->queryBuilder;
$builder = clone $this->getQueryBuilder();
if (!$builder->hasLimitOffsetClause()) {
$builder->orderBy(null);
}
Expand All @@ -349,12 +375,7 @@ protected function getIteratorCount(): int

protected function execute(): void
{
$builder = clone $this->queryBuilder;

$result = $this->connection->queryArgs(
$builder->getQuerySql(),
$builder->getQueryParameters()
);
$result = $this->connection->queryByQueryBuilder($this->getQueryBuilder());

$this->result = [];
while ($data = $result->fetch()) {
Expand Down
54 changes: 8 additions & 46 deletions src/Collection/Functions/ConjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

class ConjunctionOperatorFunction implements IArrayFunction, IQueryBuilderFunction
{
use JunctionFunctionTrait;


/** @var ConditionParser */
protected $conditionParser;

Expand Down Expand Up @@ -49,53 +52,12 @@ public function processQueryBuilderExpression(
?IDbalAggregator $aggregator = null
): DbalExpressionResult
{
$isHavingClause = false;
$processedArgs = [];
$joins = [];

foreach ($this->normalizeFunctions($args) as $collectionFunctionArgs) {
$expression = $helper->processFilterFunction($builder, $collectionFunctionArgs, $aggregator);
$expression = $expression->applyAggregator($builder);
$processedArgs[] = $expression->getExpansionArguments();
$joins = array_merge($joins, $expression->joins);
$isHavingClause = $isHavingClause || $expression->isHavingClause;
}

return new DbalExpressionResult(
return $this->processQueryBuilderExpressionWithModifier(
'%and',
[$processedArgs],
$joins,
null,
$isHavingClause,
null,
null
$helper,
$builder,
$args,
$aggregator
);
}


/**
* Normalizes directly entered column => value expression to expression array.
* @phpstan-param array<string, mixed>|list<mixed> $args
* @phpstan-return list<mixed>
*/
protected function normalizeFunctions(array $args): array
{
// Args passed as array values
// [ICollection::AND, ['id' => 1], ['name' => John]]
if (isset($args[0])) {
/** @phpstan-var list<mixed> $args */
return $args;
}

// Args passed as keys
// [ICollection::AND, 'id' => 1, 'name!=' => John]
/** @phpstan-var array<string, mixed> $args */
$processedArgs = [];
foreach ($args as $argName => $argValue) {
$functionCall = $this->conditionParser->parsePropertyOperator($argName);
$functionCall[] = $argValue;
$processedArgs[] = $functionCall;
}
return $processedArgs;
}
}
54 changes: 8 additions & 46 deletions src/Collection/Functions/DisjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

class DisjunctionOperatorFunction implements IArrayFunction, IQueryBuilderFunction
{
use JunctionFunctionTrait;


/** @var ConditionParser */
private $conditionParser;

Expand Down Expand Up @@ -49,53 +52,12 @@ public function processQueryBuilderExpression(
?IDbalAggregator $aggregator = null
): DbalExpressionResult
{
$isHavingClause = false;
$processedArgs = [];
$joins = [];

foreach ($this->normalizeFunctions($args) as $collectionFunctionArgs) {
$expression = $helper->processFilterFunction($builder, $collectionFunctionArgs, $aggregator);
$expression = $expression->applyAggregator($builder);
$processedArgs[] = $expression->getExpansionArguments();
$joins = array_merge($joins, $expression->joins);
$isHavingClause = $isHavingClause || $expression->isHavingClause;
}

return new DbalExpressionResult(
return $this->processQueryBuilderExpressionWithModifier(
'%or',
[$processedArgs],
$joins,
null,
$isHavingClause,
null,
null
$helper,
$builder,
$args,
$aggregator
);
}


/**
* Normalizes directly entered column => value expression to expression array.
* @phpstan-param array<string, mixed>|list<mixed> $args
* @phpstan-return list<mixed>
*/
protected function normalizeFunctions(array $args): array
{
// Args passed as array values
// [ICollection::AND, ['id' => 1], ['name' => John]]
if (isset($args[0])) {
/** @phpstan-var list<mixed> $args */
return $args;
}

// Args passed as keys
// [ICollection::AND, 'id' => 1, 'name!=' => John]
/** @phpstan-var array<string, mixed> $args */
$processedArgs = [];
foreach ($args as $argName => $argValue) {
$functionCall = $this->conditionParser->parsePropertyOperator($argName);
$functionCall[] = $argValue;
$processedArgs[] = $functionCall;
}
return $processedArgs;
}
}
78 changes: 78 additions & 0 deletions src/Collection/Functions/JunctionFunctionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Functions;


use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Collection\Helpers\IDbalAggregator;


/**
* @internal
*/
trait JunctionFunctionTrait
{
/**
* Normalizes directly entered column => value expression to expression array.
* @phpstan-param array<string, mixed>|list<mixed> $args
* @phpstan-return list<mixed>
*/
protected function normalizeFunctions(array $args): array
{
// Args passed as array values
// [ICollection::AND, ['id' => 1], ['name' => John]]
if (isset($args[0])) {
/** @phpstan-var list<mixed> $args */
return $args;
}

// Args passed as keys
// [ICollection::AND, 'id' => 1, 'name!=' => John]
/** @phpstan-var array<string, mixed> $args */
$processedArgs = [];
foreach ($args as $argName => $argValue) {
$functionCall = $this->conditionParser->parsePropertyOperator($argName);
$functionCall[] = $argValue;
$processedArgs[] = $functionCall;
}
return $processedArgs;
}


/**
* @param literal-string $dbalModifier either %or or %and dbal modifier
* @param array<int|string, mixed> $args
*/
protected function processQueryBuilderExpressionWithModifier(
string $dbalModifier,
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
?IDbalAggregator $aggregator
): DbalExpressionResult
{
$isHavingClause = false;
$processedArgs = [];
$joins = [];

foreach ($this->normalizeFunctions($args) as $collectionFunctionArgs) {
$expression = $helper->processFilterFunction($builder, $collectionFunctionArgs, $aggregator);
$expression = $expression->applyAggregator($builder);
$processedArgs[] = $expression->getExpansionArguments();
$joins = array_merge($joins, $expression->joins);
$isHavingClause = $isHavingClause || $expression->isHavingClause;
}

return new DbalExpressionResult(
$dbalModifier,
[$processedArgs],
$helper->mergeJoins($dbalModifier, $joins),
null,
$isHavingClause,
null,
null
);
}
}
9 changes: 5 additions & 4 deletions src/Collection/Helpers/DbalAnyAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,19 @@ public function aggregate(

$joins[] = new DbalJoinEntry(
$join->toExpression,
$join->alias,
$join->toArgs,
$join->toAlias,
"($join->onExpression) AND $joinExpression",
array_merge($join->args, $joinArgs),
array_merge($join->onArgs, $joinArgs),
$join->conventions
);

$primaryKey = $join->conventions->getStoragePrimaryKey()[0];
$queryBuilder->addGroupBy('%table.%column', $join->alias, $primaryKey);
$queryBuilder->addGroupBy('%table.%column', $join->toAlias, $primaryKey);

return new DbalExpressionResult(
'COUNT(%table.%column) > 0',
[$join->alias, $primaryKey],
[$join->toAlias, $primaryKey],
$joins,
null,
true,
Expand Down
Loading

0 comments on commit 81c1775

Please sign in to comment.