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

TASK: Verify and optimize FlowQuery operations #4699

Merged
merged 17 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2adce70
Merge branch 'task/addBehatTestsForFlowQueryCrOperations' into 90/ver…
mficzel Nov 3, 2023
1d986c9
TASK: Adjust test for the new cr
mficzel Nov 3, 2023
8e25777
TASK: Fix ParentsUntil Operation and add tests for `unique` and `remove`
mficzel Nov 3, 2023
50182cd
Merge branch 'task/addBehatTestsForFlowQueryCrOperations' into 90/ver…
mficzel Nov 3, 2023
37d901e
TASK: Fix `NextUntil` and `PrevUntil` and optimize `Next`, `NextAll`…
mficzel Nov 3, 2023
42421b4
Merge branch 'task/addBehatTestsForFlowQueryCrOperations' into 90/ver…
mficzel Nov 4, 2023
0763ed8
TASK: Disable the failing parts of `find` operation and adjust the od…
mficzel Nov 4, 2023
6c33434
Apply suggestions from code review
mficzel Nov 4, 2023
cbbfea3
TASK: Adjust format of the todo notice in `find` operation
mficzel Nov 4, 2023
3c2e461
TASK: Fix phpstan type assertation annotations
mficzel Nov 4, 2023
ba37e4a
Merge remote-tracking branch 'origin/9.0' into 90/verifyAndOptimizeFl…
mhsdesign Nov 5, 2023
45d3bd4
TASK: Add sortNames and priority to flowQuery operations
mficzel Nov 7, 2023
67f343e
Merge branch '9.0' into 90/verifyAndOptimizeFlowQuery
mficzel Nov 8, 2023
5f288c6
TASK: Add special handling of absolute node pathes since fizzle does …
mficzel Nov 10, 2023
76d0eb8
TASK: Fix find operation
mficzel Nov 10, 2023
b5a0fc7
TASK: Enable behat strict mode to fail tests without snippet implemen…
mhsdesign Nov 14, 2023
b9ce619
Merge remote-tracking branch 'origin/9.0' into 90/verifyAndOptimizeFl…
mhsdesign Feb 1, 2024
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
17 changes: 9 additions & 8 deletions .composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,20 @@
"test:functional": [
"../../bin/phpunit --colors --stop-on-failure -c ../../Build/BuildEssentials/PhpUnit/FunctionalTests.xml Neos.ContentRepository.Core/Tests/Functional"
],
"test:behat-cli": "../../bin/behat -f progress --strict --no-interaction",
"test:behavioral": [
"../../bin/behat -f progress -c Neos.ContentRepository.BehavioralTests/Tests/Behavior/behat.yml.dist",
"../../bin/behat -f progress -c Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -c Neos.ContentRepository.BehavioralTests/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -c Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/behat.yml.dist",
"../../flow doctrine:migrate --quiet; ../../flow cr:setup",
"../../bin/behat -f progress -c Neos.Neos/Tests/Behavior/behat.yml",
"../../bin/behat -f progress -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist"
"@test:behat-cli -c Neos.Neos/Tests/Behavior/behat.yml",
"@test:behat-cli -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist"
],
"test:behavioral:stop-on-failure": [
"../../bin/behat -vvv --stop-on-failure -f progress -c Neos.ContentRepository.BehavioralTests/Tests/Behavior/behat.yml.dist",
"../../bin/behat -vvv --stop-on-failure -f progress -c Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentRepository.BehavioralTests/Tests/Behavior/behat.yml.dist",
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/behat.yml.dist",
"../../flow doctrine:migrate --quiet; ../../flow cr:setup",
"../../bin/behat -vvv --stop-on-failure -f progress -c Neos.Neos/Tests/Behavior/behat.yml",
"../../bin/behat -vvv --stop-on-failure -f progress -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist"
"@test:behat-cli -vvv --stop-on-failure -c Neos.Neos/Tests/Behavior/behat.yml",
"@test:behat-cli -vvv --stop-on-failure -c Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/behat.yml.dist"
],
"test": [
"@test:unit",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,18 @@ public function count(): int
public function first(): ?Node
{
if (count($this->nodes) > 0) {
$array = $this->nodes;
return reset($array);
$key = array_key_first($this->nodes);
return $this->nodes[$key];
}

return null;
}

public function last(): ?Node
{
if (count($this->nodes) > 0) {
$key = array_key_last($this->nodes);
return $this->nodes[$key];
}

return null;
Expand All @@ -116,6 +126,10 @@ public function reverse(): self
return new self(array_reverse($this->nodes));
}

/**
* @phpstan-assert-if-false Node $this->first()
* @phpstan-assert-if-false Node $this->last()
*/
public function isEmpty(): bool
{
return $this->count() === 0;
Expand Down Expand Up @@ -183,13 +197,4 @@ public function nextAll(Node $referenceNode): self

return new self(array_slice($this->nodes, $referenceNodeIndex + 1));
}

/**
* Returns all nodes after the given $referenceNode in this set
*/
public function until(Node $referenceNode): self
{
$referenceNodeIndex = $this->getNodeIndex($referenceNode);
return new self(array_slice($this->nodes, $referenceNodeIndex + 1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
*/
final class BackReferenceNodesOperation implements OperationInterface
{
/**
* {@inheritdoc}
*
* @var string
*/
protected static $shortName = 'backReferenceNodes';

/**
* {@inheritdoc}
*
* @var integer
*/
protected static $priority = 0;

/**
* @Flow\Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@
*/
final class BackReferencesOperation implements OperationInterface
{
/**
* {@inheritdoc}
*
* @var string
*/
protected static $shortName = 'backReferences';

/**
* {@inheritdoc}
*
* @var integer
*/
protected static $priority = 0;

/**
* @Flow\Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ protected function earlyOptimizationOfFilters(FlowQuery $flowQuery, array $parse
});
$filteredFlowQuery = new FlowQuery($filteredOutput);
$filteredFlowQuery->pushOperation('filter', [$attributeFilters]);
$filteredOutput = $filteredFlowQuery->getContext();
$filteredOutput = iterator_to_array($filteredFlowQuery);
}

// Add filtered nodes to output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
*
* Example (absolute path):
*
* q(node).find('/sites/my-site/home')
* q(node).find('/<Neos.Neos:Sites>/my-site/home')
*
* Example (identifier):
*
Expand Down Expand Up @@ -118,53 +118,63 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): void
return;
}

/** @var Node[] $result */
$result = [];
$selectorAndFilter = $arguments[0];

$parsedFilter = FizzleParser::parseFilterGroup($selectorAndFilter);

/** @todo fetch them $elsewhere (fusion runtime?) */
$firstContextNode = reset($contextNodes);
assert($firstContextNode instanceof Node);
$contentRepository = $this->contentRepositoryRegistry->get($firstContextNode->subgraphIdentity->contentRepositoryId);

$entryPoints = $this->getEntryPoints($contextNodes);
foreach ($parsedFilter['Filters'] as $filter) {
$filterResults = [];
$generatedNodes = false;
if (isset($filter['IdentifierFilter'])) {
$nodeAggregateId = NodeAggregateId::fromString($filter['IdentifierFilter']);
$filterResults = $this->addNodesById($nodeAggregateId, $entryPoints, $filterResults);
$generatedNodes = true;
} elseif (isset($filter['PropertyNameFilter']) || isset($filter['PathFilter'])) {
$nodePath = AbsoluteNodePath::tryFromString($filter['PropertyNameFilter'] ?? $filter['PathFilter'])
?: NodePath::fromString($filter['PropertyNameFilter'] ?? $filter['PathFilter']);
$filterResults = $this->addNodesByPath($nodePath, $entryPoints, $filterResults);
$generatedNodes = true;
}

if (isset($filter['AttributeFilters']) && $filter['AttributeFilters'][0]['Operator'] === 'instanceof') {
$nodeTypeName = NodeTypeName::fromString($filter['AttributeFilters'][0]['Operand']);
$filterResults = $this->addNodesByType($nodeTypeName, $entryPoints, $filterResults, $contentRepository);
unset($filter['AttributeFilters'][0]);
$generatedNodes = true;
/** @var Node[] $result */
$result = [];
$selectorAndFilterParts = explode(',', $selectorAndFilter);
foreach ($selectorAndFilterParts as $selectorAndFilterPart) {

// handle absolute node pathes separately as fizzle cannot parse this syntax (yet)
if ($nodePath = AbsoluteNodePath::tryFromString($selectorAndFilterPart)) {
$nodes = $this->addNodesByPath($nodePath, $entryPoints, []);
$result = array_merge($result, $nodes);
continue;
}
if (isset($filter['AttributeFilters']) && count($filter['AttributeFilters']) > 0) {
if (!$generatedNodes) {
throw new FlowQueryException(
'find() needs an identifier, path or instanceof filter for the first filter part',
1436884196
);

$parsedFilter = FizzleParser::parseFilterGroup($selectorAndFilterPart);
$entryPoints = $this->getEntryPoints($contextNodes);
foreach ($parsedFilter['Filters'] as $filter) {
$filterResults = [];
$generatedNodes = false;
if (isset($filter['IdentifierFilter'])) {
$nodeAggregateId = NodeAggregateId::fromString($filter['IdentifierFilter']);
$filterResults = $this->addNodesById($nodeAggregateId, $entryPoints, $filterResults);
$generatedNodes = true;
} elseif (isset($filter['PropertyNameFilter']) || isset($filter['PathFilter'])) {
$nodePath = AbsoluteNodePath::tryFromString($filter['PropertyNameFilter'] ?? $filter['PathFilter'])
?: NodePath::fromString($filter['PropertyNameFilter'] ?? $filter['PathFilter']);
$filterResults = $this->addNodesByPath($nodePath, $entryPoints, $filterResults);
$generatedNodes = true;
}

if (isset($filter['AttributeFilters']) && $filter['AttributeFilters'][0]['Operator'] === 'instanceof') {
$nodeTypeName = NodeTypeName::fromString($filter['AttributeFilters'][0]['Operand']);
$filterResults = $this->addNodesByType($nodeTypeName, $entryPoints, $filterResults);
unset($filter['AttributeFilters'][0]);
$generatedNodes = true;
}
$filterQuery = new FlowQuery($filterResults);
foreach ($filter['AttributeFilters'] as $attributeFilter) {
$filterQuery->pushOperation('filter', [$attributeFilter['text']]);
if (isset($filter['AttributeFilters']) && count($filter['AttributeFilters']) > 0) {
if (!$generatedNodes) {
throw new FlowQueryException(
'find() needs an identifier, path or instanceof filter for the first filter part',
1436884196
);
}
$filterQuery = new FlowQuery($filterResults);
foreach ($filter['AttributeFilters'] as $attributeFilter) {
$filterQuery->pushOperation('filter', [$attributeFilter['text']]);
}
/** @var array<int,mixed> $filterResults */
$filterResults = iterator_to_array($filterQuery);
}
/** @var array<int,mixed> $filterResults */
$filterResults = $filterQuery->getContext();
$result = array_merge($result, $filterResults);
}
$result = array_merge($result, $filterResults);
}

$uniqueResult = [];
Expand Down Expand Up @@ -257,7 +267,7 @@ protected function addNodesByPath(NodePath|AbsoluteNodePath $nodePath, array $en
* @param array<int,Node> $result
* @return array<int,Node>
*/
protected function addNodesByType(NodeTypeName $nodeTypeName, array $entryPoints, array $result, ContentRepository $contentRepository): array
protected function addNodesByType(NodeTypeName $nodeTypeName, array $entryPoints, array $result): array
{
$nodeTypeFilter = NodeTypeCriteria::create(NodeTypeNames::with($nodeTypeName), NodeTypeNames::createEmpty());
foreach ($entryPoints as $entryPoint) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\Eel\FlowQuery\FlowQuery;
Expand All @@ -38,7 +39,7 @@ class NextAllOperation extends AbstractOperation
*
* @var integer
*/
protected static $priority = 100;
protected static $priority = 0;

/**
* @Flow\Inject
Expand Down Expand Up @@ -69,7 +70,12 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
$output = [];
$outputNodePaths = [];
foreach ($flowQuery->getContext() as $contextNode) {
foreach ($this->getNextForNode($contextNode) as $nextNode) {
$nextNodes = $this->contentRepositoryRegistry->subgraphForNode($contextNode)
->findSucceedingSiblingNodes(
$contextNode->nodeAggregateId,
FindSucceedingSiblingNodesFilter::create()
);
foreach ($nextNodes as $nextNode) {
if ($nextNode !== null && !isset($outputNodePaths[$nextNode->nodeAggregateId->value])) {
$outputNodePaths[$nextNode->nodeAggregateId->value] = true;
$output[] = $nextNode;
Expand All @@ -82,21 +88,4 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
$flowQuery->pushOperation('filter', $arguments);
}
}

/**
* @param Node $contextNode The node for which the next node should be found
* @return Nodes The next nodes of $contextNode
*/
protected function getNextForNode(Node $contextNode): Nodes
{
$subgraph = $this->contentRepositoryRegistry->subgraphForNode($contextNode);

$parentNode = $subgraph->findParentNode($contextNode->nodeAggregateId);
if ($parentNode === null) {
return Nodes::createEmpty();
}

return $subgraph->findChildNodes($parentNode->nodeAggregateId, FindChildNodesFilter::create())
->nextAll($contextNode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Eel\FlowQuery\FlowQuery;
Expand Down Expand Up @@ -69,7 +70,11 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
$output = [];
$outputNodePaths = [];
foreach ($flowQuery->getContext() as $contextNode) {
$nextNode = $this->getNextForNode($contextNode);
$nextNode = $this->contentRepositoryRegistry->subgraphForNode($contextNode)
->findSucceedingSiblingNodes(
$contextNode->nodeAggregateId,
FindSucceedingSiblingNodesFilter::create()
)->first();
if ($nextNode !== null && !isset($outputNodePaths[$nextNode->nodeAggregateId->value])) {
$outputNodePaths[$nextNode->nodeAggregateId->value] = true;
$output[] = $nextNode;
Expand All @@ -81,21 +86,4 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
$flowQuery->pushOperation('filter', $arguments);
}
}

/**
* @param Node $contextNode The node for which the preceding node should be found
* @return Node|null The following node of $contextNode or NULL
*/
protected function getNextForNode(Node $contextNode): ?Node
{
$subgraph = $this->contentRepositoryRegistry->subgraphForNode($contextNode);

$parentNode = $subgraph->findParentNode($contextNode->nodeAggregateId);
if ($parentNode === null) {
return null;
}

return $subgraph->findChildNodes($parentNode->nodeAggregateId, FindChildNodesFilter::create())
->next($contextNode);
}
}
Loading
Loading