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

Two-phase SQL rewrites #687

Merged
merged 3 commits into from
Oct 31, 2024
Merged
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: 0 additions & 1 deletion src/Collection/Aggregations/Aggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public function aggregateValues(array $values);


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression,
ExpressionContext $context,
): DbalExpressionResult;
Expand Down
3 changes: 1 addition & 2 deletions src/Collection/Aggregations/AnyAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,11 @@ public function aggregateValues(array $values): bool


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression,
ExpressionContext $context,
): DbalExpressionResult
{
if ($context !== ExpressionContext::FilterOr) {
if ($context !== ExpressionContext::FilterOrWithHavingClause) {
// When we are not in OR expression, we may simply filter the joined table by the condition.
// Otherwise, we have to employ a HAVING clause with aggregation function.
return $expression;
Expand Down
1 change: 0 additions & 1 deletion src/Collection/Aggregations/CountAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public function aggregateValues(array $values): bool


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression,
ExpressionContext $context,
): DbalExpressionResult
Expand Down
1 change: 0 additions & 1 deletion src/Collection/Aggregations/NoneAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public function aggregateValues(array $values): bool


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression,
ExpressionContext $context,
): DbalExpressionResult
Expand Down
1 change: 0 additions & 1 deletion src/Collection/Aggregations/NumericAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public function aggregateValues(array $values): mixed


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression,
ExpressionContext $context,
): DbalExpressionResult
Expand Down
13 changes: 8 additions & 5 deletions src/Collection/DbalCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ public function orderBy($expression, string $direction = ICollection::ASC): ICol

foreach ($expression as $subExpression => $subDirection) {
$collection->ordering[] = [
$helper->processExpression($collection->queryBuilder, $subExpression, ExpressionContext::FilterAnd, null),
$helper->processExpression($collection->queryBuilder, $subExpression, null),
$subDirection,
];
}
} else {
$collection->ordering[] = [
$helper->processExpression($collection->queryBuilder, $expression, ExpressionContext::ValueExpression, null),
$helper->processExpression($collection->queryBuilder, $expression, null),
$direction,
];
}
Expand Down Expand Up @@ -294,15 +294,18 @@ public function getQueryBuilder(): QueryBuilder
$expression = $helper->processExpression(
builder: $this->queryBuilder,
expression: $args,
context: ExpressionContext::FilterAnd,
aggregator: null,
);
$finalContext = $expression->havingExpression === null
? ExpressionContext::FilterAnd
: ExpressionContext::FilterAndWithHavingClause;
$expression = $expression->collect($finalContext);
$joins = $expression->joins;
$groupBy = $expression->groupBy;
if ($expression->expression !== null) {
if ($expression->expression !== null && $expression->args !== []) {
$this->queryBuilder->andWhere($expression->expression, ...$expression->args);
}
if ($expression->havingExpression !== null) {
if ($expression->havingExpression !== null && $expression->havingArgs !== []) {
$this->queryBuilder->andHaving($expression->havingExpression, ...$expression->havingArgs);
}
if ($this->mapper->getDatabasePlatform()->getName() === MySqlPlatform::NAME) {
Expand Down
4 changes: 4 additions & 0 deletions src/Collection/Expression/ExpressionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@


/**
* @internal
*
* Determines if the expression is processed for AND subtree, OR subtree or as a pure expression,
* e.g., a sorting expression.
*
Expand All @@ -13,5 +15,7 @@ enum ExpressionContext
{
case FilterAnd;
case FilterOr;
case FilterAndWithHavingClause;
case FilterOrWithHavingClause;
case ValueExpression;
}
27 changes: 18 additions & 9 deletions src/Collection/Functions/BaseCompareFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Aggregations\Aggregator;
use Nextras\Orm\Collection\Expression\ExpressionContext;
use Nextras\Orm\Collection\Functions\Result\ArrayExpressionResult;
use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Entity\IEntity;
use function array_map;
use function assert;
use function count;

Expand All @@ -34,12 +34,7 @@ public function processArrayExpression(
}

if ($valueReference->aggregator !== null) {
$values = array_map(
function ($value) use ($targetValue): bool {
return $this->evaluateInPhp($value, $targetValue);
},
$valueReference->value,
);
$values = $this->multiEvaluateInPhp($valueReference->value, $targetValue);
return new ArrayExpressionResult(
value: $values,
aggregator: $valueReference->aggregator,
Expand All @@ -59,13 +54,12 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
assert(count($args) === 2);

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

if ($expression->valueNormalizer !== null) {
$cb = $expression->valueNormalizer;
Expand All @@ -81,6 +75,21 @@ public function processDbalExpression(
abstract protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool;


/**
* @param array<mixed> $values
* @return array<mixed>
*/
protected function multiEvaluateInPhp(array $values, mixed $targetValue): array
{
return array_map(
function ($value) use ($targetValue): bool {
return $this->evaluateInPhp($value, $targetValue);
},
$values,
);
}


/**
* @param literal-string|array<literal-string|null>|null $modifier
*/
Expand Down
5 changes: 2 additions & 3 deletions src/Collection/Functions/BaseNumericAggregateFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand All @@ -64,7 +63,7 @@ public function processDbalExpression(
throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
}

return $helper->processExpression($builder, $args[0], $context, $this->aggregator)
->applyAggregator($builder, ExpressionContext::ValueExpression);
return $helper->processExpression($builder, $args[0], $this->aggregator)
->applyAggregator(ExpressionContext::ValueExpression);
}
}
1 change: 0 additions & 1 deletion src/Collection/Functions/CollectionFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult;
}
9 changes: 9 additions & 0 deletions src/Collection/Functions/CompareEqualsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool
}


protected function multiEvaluateInPhp(array $values, mixed $targetValue): array
{
if ($targetValue === null && $values === []) {
return [true];
}
return parent::multiEvaluateInPhp($values, $targetValue);
}


protected function evaluateInDb(
DbalExpressionResult $expression,
mixed $value,
Expand Down
3 changes: 1 addition & 2 deletions src/Collection/Functions/CompareLikeFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,12 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
assert(count($args) === 2);

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

$likeExpression = $args[1];
assert($likeExpression instanceof LikeExpression);
Expand Down
2 changes: 0 additions & 2 deletions src/Collection/Functions/ConjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand All @@ -112,7 +111,6 @@ public function processDbalExpression(
helper: $helper,
builder: $builder,
args: $args,
context: $context,
aggregator: $aggregator,
);
}
Expand Down
2 changes: 0 additions & 2 deletions src/Collection/Functions/DisjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand All @@ -105,7 +104,6 @@ public function processDbalExpression(
helper: $helper,
builder: $builder,
args: $args,
context: ExpressionContext::FilterOr,
aggregator: $aggregator,
);
}
Expand Down
1 change: 0 additions & 1 deletion src/Collection/Functions/FetchPropertyFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand Down
99 changes: 58 additions & 41 deletions src/Collection/Functions/JunctionFunctionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,59 +61,76 @@ protected function processQueryBuilderExpressionWithModifier(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator,
): DbalExpressionResult
{
$processedArgs = [];
$processedHavingArgs = [];
$joins = [];
$groupBy = [];
$columns = [];

[$normalized, $newAggregator] = $this->normalizeFunctions($args);
if ($newAggregator !== null) {
if ($aggregator !== null) throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
$aggregator = $newAggregator;
}

$requiresHaving = false;
$expressions = [];
foreach ($normalized as $collectionFunctionArgs) {
$expression = $helper->processExpression($builder, $collectionFunctionArgs, $context, $aggregator);
$expression = $expression->applyAggregator($builder, $context);
$whereArgs = $expression->getArgsForExpansion();
if ($whereArgs !== []) {
$processedArgs[] = $whereArgs;
}
$havingArgs = $expression->getHavingArgsForExpansion();
if ($havingArgs !== []) {
$processedHavingArgs[] = $havingArgs;
$expressions[] = $expression = $helper->processExpression($builder, $collectionFunctionArgs, $aggregator);
if ($expression->havingExpression !== null) {
$requiresHaving = true;
}
$joins = array_merge($joins, $expression->joins);
$groupBy = array_merge($groupBy, $expression->groupBy);
$columns = array_merge($columns, $expression->columns);
}

if ($context === ExpressionContext::FilterOr && $processedHavingArgs !== []) {
// move all where expressions to HAVING clause
return new DbalExpressionResult(
expression: null,
args: [],
joins: $helper->mergeJoins($dbalModifier, $joins),
groupBy: array_merge($groupBy, $columns),
havingExpression: $dbalModifier,
havingArgs: [array_merge($processedArgs, $processedHavingArgs)],
columns: [],
);
} else {
return new DbalExpressionResult(
expression: $processedArgs === [] ? null : $dbalModifier,
args: $processedArgs === [] ? [] : [$processedArgs],
joins: $helper->mergeJoins($dbalModifier, $joins),
groupBy: $groupBy,
havingExpression: $processedHavingArgs === [] ? null : $dbalModifier,
havingArgs: $processedHavingArgs === [] ? [] : [$processedHavingArgs],
columns: $columns,
);
}
return new DbalExpressionResult(
expression: $dbalModifier,
args: $expressions,
havingExpression: $requiresHaving ? $dbalModifier : null,
collectCallback: function (ExpressionContext $context) use ($helper, $dbalModifier) {
/** @var DbalExpressionResult $this */

$processedArgs = [];
$processedHavingArgs = [];
$joins = [];
$groupBy = [];
$columns = [];

if ($dbalModifier === '%or') {
if ($context === ExpressionContext::FilterAnd) {
$finalContext = ExpressionContext::FilterOr;
} elseif ($context === ExpressionContext::FilterAndWithHavingClause) {
$finalContext = ExpressionContext::FilterOrWithHavingClause;
} else {
$finalContext = $context;
}
} else {
$finalContext = $context;
}

foreach ($this->args as $arg) {
assert($arg instanceof DbalExpressionResult);
$expression = $arg->collect($finalContext)->applyAggregator($finalContext);

$whereArgs = $expression->getArgsForExpansion();
if ($whereArgs !== []) {
$processedArgs[] = $whereArgs;
}
$havingArgs = $expression->getHavingArgsForExpansion();
if ($havingArgs !== []) {
$processedHavingArgs[] = $havingArgs;
}
$joins = array_merge($joins, $expression->joins);
$groupBy = array_merge($groupBy, $expression->groupBy);
$columns = array_merge($columns, $expression->columns);
}

return new DbalExpressionResult(
expression: $processedArgs === [] ? null : $dbalModifier,
args: $processedArgs === [] ? [] : [$processedArgs],
joins: $helper->mergeJoins($dbalModifier, $joins),
groupBy: $groupBy,
havingExpression: $processedHavingArgs === [] ? null : $dbalModifier,
havingArgs: $processedHavingArgs === [] ? [] : [$processedHavingArgs],
columns: $columns,
);
},
);
}
}
Loading
Loading