From 9ce38469b8552fa8fb101424f1930e494392fea3 Mon Sep 17 00:00:00 2001 From: Jan Philipp Pietrzyk Date: Wed, 4 Oct 2017 09:24:40 +0200 Subject: [PATCH 1/2] Fix root node select for tree --- src/NestedSetQueryFactory.php | 15 +++++++-- tests/NestedSetQueryFactoryTest.php | 51 ++++++++++++++++++++++++++--- tests/_bootstrap.php | 2 +- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/NestedSetQueryFactory.php b/src/NestedSetQueryFactory.php index 4e0686e..c7f50f8 100644 --- a/src/NestedSetQueryFactory.php +++ b/src/NestedSetQueryFactory.php @@ -146,6 +146,7 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre "{$queryAlias}directNode." . $this->leftCol, "{$queryAlias}directNode." . $this->rightCol, "{$queryAlias}directNode." . $this->levelCol, + "{$queryAlias}directNode." . $this->rootCol, ]) ->from($this->connection->quoteIdentifier($tableExpression), "{$queryAlias}directNode") ->andWhere("{$queryAlias}directNode.{$this->pkCol} IN (:{$queryAlias}nodeIds)"); @@ -155,6 +156,7 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre "{$queryAlias}SiblingNode." . $this->leftCol, "{$queryAlias}SiblingNode." . $this->rightCol, "{$queryAlias}SiblingNode." . $this->levelCol, + "{$queryAlias}SiblingNode." . $this->rootCol, ]) ->from($this->connection->quoteIdentifier($tableExpression), "{$queryAlias}SiblingNode") ->innerJoin( @@ -164,7 +166,9 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre " {$queryAlias}SiblingNode.{$this->leftCol} >= {$queryAlias}SelectedNode.{$this->leftCol} AND {$queryAlias}SiblingNode.{$this->rightCol} >= {$queryAlias}SelectedNode.{$this->rightCol} - AND {$queryAlias}SiblingNode.{$this->levelCol} = {$queryAlias}SelectedNode.{$this->levelCol}" + AND {$queryAlias}SiblingNode.{$this->levelCol} = {$queryAlias}SelectedNode.{$this->levelCol} + AND {$queryAlias}SiblingNode.{$this->rootCol} = {$queryAlias}SelectedNode.{$this->rootCol} + " ); $childrenQuery = $this->connection->createQueryBuilder() @@ -172,6 +176,7 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre "{$queryAlias}ChildNode." . $this->leftCol, "{$queryAlias}ChildNode." . $this->rightCol, "{$queryAlias}ChildNode." . $this->levelCol, + "{$queryAlias}ChildNode." . $this->rootCol, ]) ->from($this->connection->quoteIdentifier($tableExpression), "{$queryAlias}ChildNode") ->innerJoin( @@ -181,7 +186,9 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre " {$queryAlias}ChildNode.{$this->leftCol} > {$queryAlias}SelectedNode.{$this->leftCol} AND {$queryAlias}ChildNode.{$this->rightCol} < {$queryAlias}SelectedNode.{$this->rightCol} - AND {$queryAlias}ChildNode.{$this->levelCol} <= ({$queryAlias}SelectedNode.{$this->levelCol} + :{$queryAlias}maxChildLevel)" + AND {$queryAlias}ChildNode.{$this->levelCol} <= ({$queryAlias}SelectedNode.{$this->levelCol} + :{$queryAlias}maxChildLevel) + AND {$queryAlias}ChildNode.{$this->rootCol} = {$queryAlias}SelectedNode.{$this->rootCol} + " ); $idQuery = $this->connection->createQueryBuilder() @@ -193,7 +200,9 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre "{$queryAlias}SourceNode", " {$queryAlias}Group.{$this->leftCol} <= {$queryAlias}SourceNode.{$this->leftCol} - AND {$queryAlias}Group.{$this->rightCol} >= {$queryAlias}SourceNode.{$this->rightCol}" + AND {$queryAlias}Group.{$this->rightCol} >= {$queryAlias}SourceNode.{$this->rightCol} + AND {$queryAlias}Group.{$this->rootCol} = {$queryAlias}SourceNode.{$this->rootCol} + " ) ->groupBy("{$queryAlias}Group.id"); diff --git a/tests/NestedSetQueryFactoryTest.php b/tests/NestedSetQueryFactoryTest.php index c6b1dcf..e2985da 100644 --- a/tests/NestedSetQueryFactoryTest.php +++ b/tests/NestedSetQueryFactoryTest.php @@ -19,6 +19,7 @@ public function setUp() $connection = \NestedSetBootstrap::getConnection(); \NestedSetBootstrap::importTable(); \NestedSetBootstrap::insertDemoTree(); + \NestedSetBootstrap::insertDemoTree(2); $this->queryFactory = NestedSetFactory::createQueryFactory($connection, new NestedSetConfig('id', 'left', 'right', 'level')); } @@ -99,12 +100,52 @@ public function test_fetch_all_roots() $rows = $qb->execute()->fetchAll(); - $this->assertCount(1, $rows); + $this->assertCount(2, $rows); $this->assertEquals('Clothing', $rows[0]['name']); + $this->assertEquals('Clothing', $rows[1]['name']); + } + + public function test_fetch_subtree_with_a_single_selected_node() + { + \NestedSetBootstrap::printTree(1); + + $qb = $this->queryFactory + ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [5]) + ->select('*'); + + $this->assertSubTree( + [ + 'Clothing', + 'Mens', + 'Suits', + 'Slacks', + 'Jackets', + 'Women', + ], + $qb->execute()->fetchAll() + ); + } + + public function test_fetch_subtree_with_root_only_selected() + { + + $qb = $this->queryFactory + ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [1]) + ->select('*'); + + $this->assertSubTree( + [ + 'Clothing', + 'Mens', + 'Women', + ], + $qb->execute()->fetchAll() + ); } public function test_fetch_subtree_with_selected_nodes() { + $qb = $this->queryFactory ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [2, 7]) ->select('*'); @@ -181,10 +222,10 @@ public function test_fetch_subtree_with_selected_nodes_uses_the_depth_parameter( private function assertSubTree(array $expectedNames, array $rows) { - $this->assertCount(count($expectedNames), $rows, print_r($rows, true)); + $names = array_map(function(array $node) { + return $node['name']; + }, $rows); - foreach ($expectedNames as $index => $name) { - $this->assertEquals($name, $rows[$index]['name']); - } + $this->assertEquals($expectedNames, $names, 'Got: ' . print_r($names, true)); } } diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php index b143103..a71b78f 100644 --- a/tests/_bootstrap.php +++ b/tests/_bootstrap.php @@ -109,7 +109,7 @@ public static function insertDemoTree(int $rootId = 1) foreach ($data as list($id, $left, $right, $level, $name)) { self::getConnection()->insert('tree', [ - '`id`' => $id, + '`id`' => $id + (20 * ($rootId - 1)), '`left`' => $left, '`right`' => $right, '`level`' => $level, From 29b737b925c39ba18c5df305b914c45214a9ffdd Mon Sep 17 00:00:00 2001 From: Jan Philipp Pietrzyk Date: Wed, 4 Oct 2017 12:05:54 +0200 Subject: [PATCH 2/2] Fix select of siblings --- src/NestedSetQueryFactory.php | 56 +++++++++++++++++++++-------- tests/NestedSetQueryFactoryTest.php | 32 +++++++++-------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/NestedSetQueryFactory.php b/src/NestedSetQueryFactory.php index c7f50f8..6924659 100644 --- a/src/NestedSetQueryFactory.php +++ b/src/NestedSetQueryFactory.php @@ -143,6 +143,7 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre $directNodeSubSelect = $this->connection->createQueryBuilder() ->select([ + "{$queryAlias}directNode." . $this->pkCol, "{$queryAlias}directNode." . $this->leftCol, "{$queryAlias}directNode." . $this->rightCol, "{$queryAlias}directNode." . $this->levelCol, @@ -151,8 +152,10 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre ->from($this->connection->quoteIdentifier($tableExpression), "{$queryAlias}directNode") ->andWhere("{$queryAlias}directNode.{$this->pkCol} IN (:{$queryAlias}nodeIds)"); - $siblingQuery = $this->connection->createQueryBuilder() + + $parentQuery = $this->connection->createQueryBuilder() ->select([ + "{$queryAlias}SiblingNode." . $this->pkCol, "{$queryAlias}SiblingNode." . $this->leftCol, "{$queryAlias}SiblingNode." . $this->rightCol, "{$queryAlias}SiblingNode." . $this->levelCol, @@ -161,18 +164,29 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre ->from($this->connection->quoteIdentifier($tableExpression), "{$queryAlias}SiblingNode") ->innerJoin( "{$queryAlias}SiblingNode", + $this->connection->quoteIdentifier($tableExpression), + "{$queryAlias}ParentNode", + " + {$queryAlias}SiblingNode.{$this->leftCol} >= {$queryAlias}ParentNode.{$this->leftCol} + AND {$queryAlias}SiblingNode.{$this->rightCol} <= {$queryAlias}ParentNode.{$this->rightCol} + AND {$queryAlias}SiblingNode.{$this->levelCol} = {$queryAlias}ParentNode.{$this->levelCol} + 1 + AND {$queryAlias}SiblingNode.{$this->rootCol} = {$queryAlias}ParentNode.{$this->rootCol} + " + ) + ->innerJoin( + "{$queryAlias}ParentNode", '(' . $directNodeSubSelect->getSQL() . ')', "{$queryAlias}SelectedNode", " - {$queryAlias}SiblingNode.{$this->leftCol} >= {$queryAlias}SelectedNode.{$this->leftCol} - AND {$queryAlias}SiblingNode.{$this->rightCol} >= {$queryAlias}SelectedNode.{$this->rightCol} - AND {$queryAlias}SiblingNode.{$this->levelCol} = {$queryAlias}SelectedNode.{$this->levelCol} - AND {$queryAlias}SiblingNode.{$this->rootCol} = {$queryAlias}SelectedNode.{$this->rootCol} + {$queryAlias}ParentNode.{$this->leftCol} < {$queryAlias}SelectedNode.{$this->leftCol} + AND {$queryAlias}ParentNode.{$this->rightCol} > {$queryAlias}SelectedNode.{$this->rightCol} + AND {$queryAlias}ParentNode.{$this->rootCol} = {$queryAlias}SelectedNode.{$this->rootCol} " ); $childrenQuery = $this->connection->createQueryBuilder() ->select([ + "{$queryAlias}ChildNode." . $this->pkCol, "{$queryAlias}ChildNode." . $this->leftCol, "{$queryAlias}ChildNode." . $this->rightCol, "{$queryAlias}ChildNode." . $this->levelCol, @@ -191,18 +205,30 @@ public function createSubtreeThroughMultipleNodesQueryBuilder(string $tableExpre " ); - $idQuery = $this->connection->createQueryBuilder() - ->select("{$queryAlias}Group.{$this->pkCol}") - ->from($this->connection->quoteIdentifier($tableExpression), "{$queryAlias}Group") + $rootQuery = $this->connection->createQueryBuilder() + ->select([ + "{$queryAlias}RootNode." . $this->pkCol, + "{$queryAlias}RootNode." . $this->leftCol, + "{$queryAlias}RootNode." . $this->rightCol, + "{$queryAlias}RootNode." . $this->levelCol, + "{$queryAlias}RootNode." . $this->rootCol, + ]) + ->from($this->connection->quoteIdentifier($tableExpression), "{$queryAlias}RootNode") ->innerJoin( - "{$queryAlias}Group", - '((' . $childrenQuery->getSQL() . ') UNION (' . $siblingQuery->getSQL() . '))', - "{$queryAlias}SourceNode", - " - {$queryAlias}Group.{$this->leftCol} <= {$queryAlias}SourceNode.{$this->leftCol} - AND {$queryAlias}Group.{$this->rightCol} >= {$queryAlias}SourceNode.{$this->rightCol} - AND {$queryAlias}Group.{$this->rootCol} = {$queryAlias}SourceNode.{$this->rootCol} + "{$queryAlias}RootNode", + '(' . $directNodeSubSelect->getSQL() . ')', + "{$queryAlias}SelectedNode", + " + {$queryAlias}RootNode.{$this->levelCol} = 0 + AND {$queryAlias}RootNode.{$this->rootCol} = {$queryAlias}SelectedNode.{$this->rootCol} " + ); + + $idQuery = $this->connection->createQueryBuilder() + ->select("{$queryAlias}Group.{$this->pkCol}") + ->from( + '((' . $childrenQuery->getSQL() . ') UNION (' . $parentQuery->getSQL() . ') UNION (' . $rootQuery->getSQL() . ')) ', + "{$queryAlias}Group" ) ->groupBy("{$queryAlias}Group.id"); diff --git a/tests/NestedSetQueryFactoryTest.php b/tests/NestedSetQueryFactoryTest.php index e2985da..14b0089 100644 --- a/tests/NestedSetQueryFactoryTest.php +++ b/tests/NestedSetQueryFactoryTest.php @@ -105,47 +105,43 @@ public function test_fetch_all_roots() $this->assertEquals('Clothing', $rows[1]['name']); } - public function test_fetch_subtree_with_a_single_selected_node() + public function test_fetch_subtree_with_root_only_selected() { - \NestedSetBootstrap::printTree(1); - $qb = $this->queryFactory - ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [5]) + ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [1]) ->select('*'); $this->assertSubTree( [ 'Clothing', 'Mens', - 'Suits', - 'Slacks', - 'Jackets', 'Women', ], $qb->execute()->fetchAll() ); } - public function test_fetch_subtree_with_root_only_selected() + public function test_fetch_subtree_with_a_single_selected_node_slacks() { - $qb = $this->queryFactory - ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [1]) + ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [5]) ->select('*'); $this->assertSubTree( [ 'Clothing', 'Mens', + 'Suits', + 'Slacks', + 'Jackets', 'Women', ], $qb->execute()->fetchAll() ); } - public function test_fetch_subtree_with_selected_nodes() + public function test_fetch_subtree_with_selected_nodes_mens_and_dresses() { - $qb = $this->queryFactory ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [2, 7]) ->select('*'); @@ -164,7 +160,10 @@ public function test_fetch_subtree_with_selected_nodes() ], $qb->execute()->fetchAll() ); + } + public function test_fetch_subtree_with_selected_nodes_mens_and_women() + { $qb = $this->queryFactory ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [3, 2]) ->select('*'); @@ -183,7 +182,7 @@ public function test_fetch_subtree_with_selected_nodes() ); } - public function test_fetch_subtree_with_selected_nodes_uses_the_depth_parameter() + public function test_fetch_subtree_with_selected_nodes_with_a_two_as_a_depth_parameter() { $qb = $this->queryFactory ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [2, 3], 2) @@ -205,7 +204,10 @@ public function test_fetch_subtree_with_selected_nodes_uses_the_depth_parameter( ], $qb->execute()->fetchAll() ); + } + public function test_fetch_subtree_with_selected_nodes_with_a_zero_depth_parameter() + { $qb = $this->queryFactory ->createSubtreeThroughMultipleNodesQueryBuilder('tree', 't', 'root_id', [3, 2], 0) ->select('*'); @@ -222,10 +224,10 @@ public function test_fetch_subtree_with_selected_nodes_uses_the_depth_parameter( private function assertSubTree(array $expectedNames, array $rows) { - $names = array_map(function(array $node) { + $names = array_map(function (array $node) { return $node['name']; }, $rows); - $this->assertEquals($expectedNames, $names, 'Got: ' . print_r($names, true)); + $this->assertEquals($expectedNames, $names, 'Got: ' . print_r($names, true) . "\n and expected: " . print_r($expectedNames, true)); } }