Author: dr
Date: Mon Aug 6 12:00:15 2007
New Revision: 5820
Log:
- Added the nested set implementation.
Added:
trunk/Tree/src/backends/db_nested_set.php (with props)
trunk/Tree/tests/db_nested_set_tree.php (with props)
trunk/Tree/tests/files/nested_set.dba
Modified:
trunk/Tree/design/class_diagram.png
trunk/Tree/src/tree_autoload.php
trunk/Tree/tests/suite.php
trunk/Tree/tests/tree.php
Modified: trunk/Tree/design/class_diagram.png
==============================================================================
Binary files - no diff available.
Added: trunk/Tree/src/backends/db_nested_set.php
==============================================================================
--- trunk/Tree/src/backends/db_nested_set.php (added)
+++ trunk/Tree/src/backends/db_nested_set.php [iso-8859-1] Mon Aug 6 12:00:15
2007
@@ -1,0 +1,686 @@
+<?php
+/**
+ * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved.
+ * @license http://ez.no/licenses/new_bsd New BSD License
+ * @version //autogentag//
+ * @filesource
+ * @package Tree
+ */
+
+/**
+ * ezcTreeDbNestedSet implements a tree backend which stores parent/child
+ * information with left and right values.
+ *
+ * The table that stores the index (configured using the $indexTableName
argument
+ * of the [EMAIL PROTECTED] __construct} method) should contain atleast four
fields. The
+ * first one 'id' will contain the node's ID, the second one 'parent_id' the ID
+ * of the node's parent. Both fields should be of the same database field type.
+ * Supported field types are either integer or a string type. The other two
+ * fields "lft" and "rgt" will store the left and right values that the
+ * algorithm requires. These two fields should be of an integer type.
+ *
+ * @property-read ezcTreeXmlDataStore $store
+ * The data store that is used for retrieving/storing data.
+ * @property bool $prefetch
+ * Whether data pre-fetching is enabled.
+ * @property string $nodeClassName
+ * Which class is used as tree node - this class *must* inherit
+ * the ezcTreeNode class.
+ *
+ * @package Tree
+ * @version //autogentag//
+ * @mainclass
+ */
+class ezcTreeDbNestedSet extends ezcTreeDb
+{
+ /**
+ * Creates a new ezcTreeDbNestedSet object.
+ *
+ * The different arguments to the method configure which database
+ * connection ($dbh) is used to access the database and the $indexTableName
+ * argument which table is used to retrieve the relation data from. The
+ * $store argument configure which data store is used with this tree.
+ *
+ * It is up to the user to create the database table and make sure it is
+ * empty.
+ *
+ * @param ezcDbHandler $dbh
+ * @param string $indexTableName
+ * @param ezcTreeDbDataStore $store
+ */
+ public static function create( ezcDbHandler $dbh, $indexTableName,
ezcTreeDbDataStore $store )
+ {
+ return new ezcTreeDbNestedSet( $dbh, $indexTableName, $store );
+ }
+
+ /**
+ * Returns the ID of parent of the node with ID $id
+ *
+ * @param string $id
+ * @return string
+ */
+ protected function getParentId( $id )
+ {
+ $db = $this->dbh;
+ $q = $db->createSelectQuery();
+
+ // SELECT id, parent_id
+ // FROM indexTable
+ // WHERE id = $id
+ $q->select( 'id, parent_id' )
+ ->from( $db->quoteIdentifier( $this->indexTableName ) )
+ ->where( $q->expr->eq( 'id', $q->bindValue( $id ) ) );
+
+ $s = $q->prepare();
+ $s->execute();
+ $row = $s->fetch();
+ return $row['parent_id'];
+ }
+
+
+ /**
+ * Runs SQL to get all the children of the node with ID $nodeId as a PDO
+ * result set
+ *
+ * @param string $nodeId
+ * @return PDOStatement
+ */
+ protected function fetchChildRecords( $nodeId )
+ {
+ $db = $this->dbh;
+ $q = $db->createSelectQuery();
+
+ // SELECT id, parent_id
+ // FROM indexTable
+ // WHERE parent_id = $nodeId
+ $q->select( 'id, parent_id' )
+ ->from( $db->quoteIdentifier( $this->indexTableName ) )
+ ->where( $q->expr->eq( 'parent_id', $q->bindValue( $nodeId ) ) );
+
+ $s = $q->prepare();
+ $s->execute();
+ return $s;
+ }
+
+ /**
+ * Returns all the children of the node with ID $id.
+ *
+ * @param string $id
+ * @return ezcTreeNodeList
+ */
+ public function fetchChildren( $id )
+ {
+ $className = $this->properties['nodeClassName'];
+ $list = new ezcTreeNodeList;
+ foreach ( $this->fetchChildRecords( $id ) as $record )
+ {
+ $list->addNode( new $className( $this, $record['id'] ) );
+ }
+ return $list;
+ }
+
+ /**
+ * Returns all the nodes in the path from the root node to the node with ID
+ * $id, including those two nodes.
+ *
+ * @param string $id
+ * @return ezcTreeNodeList
+ */
+ public function fetchPath( $id )
+ {
+ $className = $this->properties['nodeClassName'];
+ $list = new ezcTreeNodeList;
+
+ $db = $this->dbh;
+ $q = $db->createSelectQuery();
+
+ // SELECT parent.id
+ // FROM indexTable as node,
+ // indexTable as parent
+ // WHERE
+ // node.lft BETWEEN parent.lft AND parent.rgt
+ // AND
+ // node.if = $id
+ // ORDER BY parent.lft
+ $q->select( 'parent.id' )
+ ->from( $db->quoteIdentifier( $this->indexTableName ) . " as node" )
+ ->from( $db->quoteIdentifier( $this->indexTableName ) . " as parent"
)
+ ->where( $q->expr->lAnd(
+ $q->expr->between( 'node.lft', 'parent.lft', 'parent.rgt' ),
+ $q->expr->eq( 'node.id', $q->bindValue( $id ) )
+ ))
+ ->orderBy( 'parent.lft' );
+
+ $s = $q->prepare();
+ $s->execute();
+
+ foreach ( $s as $result )
+ {
+ $list->addNode( new $className( $this, $result['id'] ) );
+ }
+ return $list;
+ }
+
+ /**
+ * Returns the node with ID $id and all its children, sorted accoring to
+ * the `Depth-first sorting`_ algorithm.
+ *
+ * @param string $id
+ * @return ezcTreeNodeList
+ */
+ public function fetchSubtreeDepthFirst( $nodeId )
+ {
+ $className = $this->properties['nodeClassName'];
+ $list = new ezcTreeNodeList;
+ $db = $this->dbh;
+
+ // Fetch parent information
+ list( $left, $right, $width ) = $this->fetchNodeInformation( $nodeId );
+
+ // Fetch subtree
+ // SELECT id
+ // FROM indexTable
+ // WHERE lft BETWEEN $left AND $right
+ // ORDER BY lft
+ $q = $db->createSelectQuery();
+ $q->select( 'id' )
+ ->from( $db->quoteIdentifier( $this->indexTableName ) )
+ ->where( $q->expr->between( 'lft', $q->bindValue( $left ),
$q->bindValue( $right ) ) )
+ ->orderBy( 'lft' );
+ $s = $q->prepare();
+ $s->execute();
+
+ foreach ( $s as $result )
+ {
+ $list->addNode( new $className( $this, $result['id'] ) );
+ }
+ return $list;
+ }
+
+ /**
+ * Alias for fetchSubtreeDepthFirst().
+ *
+ * @param string $id
+ * @return ezcTreeNodeList
+ */
+ public function fetchSubtree( $nodeId )
+ {
+ return $this->fetchSubtreeDepthFirst( $nodeId );
+ }
+
+ /**
+ * Adds the children nodes of the node with ID $nodeId to the
+ * ezcTreeNodeList $list.
+ *
+ * @param ezcTreeNodeList $list
+ * @param string $nodeId
+ */
+ protected function addChildNodesBreadthFirst( ezcTreeNodeList $list,
$nodeId )
+ {
+ $className = $this->properties['nodeClassName'];
+ $childRecords = $this->fetchChildRecords( $nodeId )->fetchAll();
+ foreach ( $childRecords as $record )
+ {
+ $list->addNode( new $className( $this, $record['id'] ) );
+ }
+ foreach ( $childRecords as $record )
+ {
+ $this->addChildNodesBreadthFirst( $list, $record['id'] );
+ }
+ }
+
+ /**
+ * Returns the node with ID $id and all its children, sorted accoring to
+ * the `Breadth-first sorting`_ algorithm.
+ *
+ * @param string $id
+ * @return ezcTreeNodeList
+ */
+ public function fetchSubtreeBreadthFirst( $nodeId )
+ {
+ $className = $this->properties['nodeClassName'];
+ $list = new ezcTreeNodeList;
+ $list->addNode( new $className( $this, $nodeId ) );
+ $this->addChildNodesBreadthFirst( $list, $nodeId );
+ return $list;
+ }
+
+ /**
+ * Returns the number of direct children of the node with ID $id
+ *
+ * @param string $id
+ * @return int
+ */
+ public function getChildCount( $nodeId )
+ {
+ $db = $this->dbh;
+ $q = $db->createSelectQuery();
+
+ // SELECT count(id)
+ // FROM indexTable
+ // WHERE parent_id = $nodeId
+ $q->select( 'count(id)' )
+ ->from( $db->quoteIdentifier( $this->indexTableName ) )
+ ->where( $q->expr->eq( 'parent_id', $q->bindValue( $nodeId ) ) );
+
+ $s = $q->prepare();
+ $s->execute();
+ return (int) $s->fetchColumn(0);
+ }
+
+ /**
+ * Adds the number of children with for the node with ID $nodeId nodes to
+ * $count recursively.
+ *
+ * @param int &$count
+ * @param string $nodeId
+ */
+ protected function countChildNodes( &$count, $nodeId )
+ {
+ foreach ( $this->fetchChildRecords( $nodeId ) as $record )
+ {
+ $count++;
+ $this->countChildNodes( $count, $record['id'] );
+ }
+ }
+
+ /**
+ * Returns the number of children of the node with ID $id, recursively
+ *
+ * @param string $id
+ * @return int
+ */
+ public function getChildCountRecursive( $id )
+ {
+ $count = 0;
+ $this->countChildNodes( $count, $id );
+ return $count;
+ }
+
+ /**
+ * Returns the distance from the root node to the node with ID $id
+ *
+ * @param string $id
+ * @return int
+ */
+ public function getPathLength( $id )
+ {
+ $id = $this->getParentId( $id );
+ $length = 0;
+
+ while ( $id !== null )
+ {
+ $id = $this->getParentId( $id );
+ $length++;
+ }
+ return $length;
+ }
+
+ /**
+ * Returns whether the node with ID $id has children
+ *
+ * @param string $id
+ * @return bool
+ */
+ public function hasChildNodes( $nodeId )
+ {
+ return $this->getChildCount( $nodeId ) > 0;
+ }
+
+ /**
+ * Returns whether the node with ID $childId is a direct child of the node
+ * with ID $parentId
+ *
+ * @param string $childId
+ * @param string $parentId
+ * @return bool
+ */
+ public function isChildOf( $childId, $parentId )
+ {
+ $id = $this->getParentId( $childId );
+ $parentId = (string) $parentId;
+ return $parentId === $id;
+ }
+
+ /**
+ * Returns whether the node with ID $childId is a direct or indirect child
+ * of the node with ID $parentId
+ *
+ * @param string $childId
+ * @param string $parentId
+ * @return bool
+ */
+ public function isDecendantOf( $childId, $parentId )
+ {
+ $parentId = (string) $parentId;
+ $id = $childId;
+ do
+ {
+ $id = $this->getParentId( $id );
+ if ( $parentId === $id )
+ {
+ return true;
+ }
+ } while ( $id !== null );
+ return false;
+ }
+
+ /**
+ * Returns whether the nodes with IDs $child1Id and $child2Id are siblings
+ * (ie, the share the same parent)
+ *
+ * @param string $child1Id
+ * @param string $child2Id
+ * @return bool
+ */
+ public function isSiblingOf( $child1Id, $child2Id )
+ {
+ $id1 = $this->getParentId( $child1Id );
+ $id2 = $this->getParentId( $child2Id );
+ return $id1 === $id2 && (string) $child1Id !== (string) $child2Id;
+ }
+
+ /**
+ * Sets a new node as root node, this wipes also out the whole tree
+ *
+ * @param ezcTreeNode $node
+ */
+ public function setRootNode( ezcTreeNode $node )
+ {
+ $db = $this->dbh;
+
+ // Remove nodes from tree
+ // DELETE FROM indexTable
+ $q = $db->createDeleteQuery();
+ $q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) );
+ $s = $q->prepare();
+ $s->execute();
+
+ // Remove all data belonging to those nodes
+ $this->store->deleteDataForAllNodes();
+
+ // Create new root node
+ // INSERT INTO indexTable
+ // SET parent_id = null,
+ // id = $node->id,
+ // lft = 1, rgt = 2
+ $q = $db->createInsertQuery();
+ $q->insertInto( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'parent_id', "null" )
+ ->set( 'id', $q->bindValue( $node->id ) )
+ ->set( 'lft', 1 )
+ ->set( 'rgt', 2 );
+ $s = $q->prepare();
+ $s->execute();
+
+ // Store data for new root node
+ $this->store->storeDataForNode( $node, $node->data );
+ }
+
+ /**
+ * Updates the left and right values of the nodes that are added while
+ * adding a whole subtree as child of a node.
+ *
+ * The method does not update nodes where the IDs are in the $excludedIds
+ * list.
+ *
+ * @param int $right
+ * @param int $width
+ * @param array(string) $excludedIds
+ */
+ protected function updateNestedValuesForSubtreeAddition( $right, $width,
$excludedIds = array() )
+ {
+ $db = $this->dbh;
+
+ // Move all the right values + $width for nodes where the the right
value >=
+ // the parent right value:
+ // UPDATE indexTable
+ // SET rgt = rgt + $width
+ // WHERE rgt >= $right
+ $q = $db->createUpdateQuery();
+ $q->update( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'rgt', $q->expr->add( 'rgt', $width ) )
+ ->where( $q->expr->gte( 'rgt', $right ) );
+ if ( count( $excludedIds ) )
+ {
+ $q->where( $q->expr->not( $q->expr->in( 'id', $excludedIds ) ) );
+ }
+ $q->prepare()->execute();
+
+ // Move all the left values + $width for nodes where the the right
value >=
+ // the parent left value
+ // UPDATE indexTable
+ // SET lft = lft + $width
+ // WHERE lft >= $right
+ $q = $db->createUpdateQuery();
+ $q->update( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'lft', $q->expr->add( 'lft', $width ) )
+ ->where( $q->expr->gte( 'lft', $right ) );
+ if ( count( $excludedIds ) )
+ {
+ $q->where( $q->expr->not( $q->expr->in( 'id', $excludedIds ) ) );
+ }
+ $q->prepare()->execute();
+ }
+
+ /**
+ * Adds the node $childNode as child of the node with ID $parentId
+ *
+ * @param string $parentId
+ * @paran ezcTreeNode $childNode
+ */
+ public function addChild( $parentId, ezcTreeNode $childNode )
+ {
+ if ( $this->inTransaction )
+ {
+ $this->addTransactionItem( new ezcTreeTransactionItem(
ezcTreeTransactionItem::ADD, $childNode, null, $parentId ) );
+ return;
+ }
+
+ $db = $this->dbh;
+
+ // Fetch parent's information
+ list( $left, $right, $width ) = $this->fetchNodeInformation( $parentId
);
+
+ // Update left and right values to account for new subtree
+ $this->updateNestedValuesForSubtreeAddition( $right, $width );
+
+ // Add new node
+ if ( $width == 2 )
+ {
+ $newLeft = $left + 1;
+ $newRight = $left + 2;
+ }
+ else
+ {
+ $newLeft = $right;
+ $newRight = $right + 1;
+ }
+
+ // INSERT INTO indexTable
+ // SET parent_id = $parentId,
+ // id = $childNode->id,
+ // lft = $newLeft,
+ // rgt = $newRight
+ $q = $db->createInsertQuery();
+ $q->insertInto( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'parent_id', $q->bindValue( $parentId ) )
+ ->set( 'id', $q->bindValue( $childNode->id ) )
+ ->set( 'lft', $q->bindValue( $newLeft ) )
+ ->set( 'rgt', $q->bindValue( $newRight ) );
+ $s = $q->prepare();
+ $s->execute();
+
+ // Add the data for the new node
+ $this->store->storeDataForNode( $childNode, $childNode->data );
+ }
+
+ /**
+ * Returns the left, right and width values for the node with ID $id as an
+ * array.
+ *
+ * The format of the array is:
+ * - 0: left value
+ * - 1: right value
+ * - 2: width value (right - left + 1)
+ *
+ * @param string $id
+ * @return array(int)
+ */
+ protected function fetchNodeInformation( $id )
+ {
+ $db = $this->dbh;
+
+ // SELECT lft, rgt, rft-lft+1 as width
+ // FROM indexTable
+ // WHERE id = $id
+ $q = $db->createSelectQuery();
+ $q->select( 'lft, rgt, rgt - lft + 1 as width' )
+ ->from( $db->quoteIdentifier( $this->indexTableName ) )
+ ->where( $q->expr->eq( 'id', $q->bindValue( $id ) ) );
+ $s = $q->prepare();
+ $s->execute();
+ $r = $s->fetchAll( PDO::FETCH_NUM );
+ return $r[0];
+ }
+
+ /**
+ * Updates the left and right values in case a subtree is deleted
+ *
+ * @param int $right
+ * @param int $width
+ */
+ protected function updateNestedValuesForSubtreeDeletion( $right, $width )
+ {
+ $db = $this->dbh;
+
+ // Move all the right values + $width for nodes where the the right
+ // value > the parent right value
+ // UPDATE indexTable
+ // SET rgt = rgt - $width
+ // WHERE rgt > $right
+ $q = $db->createUpdateQuery();
+ $q->update( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'rgt', $q->expr->sub( 'rgt', $width ) )
+ ->where( $q->expr->gt( 'rgt', $right ) );
+ $q->prepare()->execute();
+
+ // Move all the right values + $width for nodes where the the left
+ // value > the parent right value
+ // UPDATE indexTable
+ // SET lft = lft - $width
+ // WHERE lft > $right
+ $q = $db->createUpdateQuery();
+ $q->update( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'lft', $q->expr->sub( 'lft', $width ) )
+ ->where( $q->expr->gt( 'lft', $right ) );
+ $q->prepare()->execute();
+ }
+
+ /**
+ * Deletes the node with ID $id from the tree, including all its children
+ *
+ * @param string $id
+ */
+ public function delete( $id )
+ {
+ if ( $this->inTransaction )
+ {
+ $this->addTransactionItem( new ezcTreeTransactionItem(
ezcTreeTransactionItem::DELETE, null, $id ) );
+ return;
+ }
+
+ // Delete all data for the deleted nodes
+ $nodeList = $this->fetchSubtree( $id );
+ $this->store->deleteDataForNodes( $nodeList );
+
+ // Fetch node information
+ list( $left, $right, $width ) = $this->fetchNodeInformation( $id );
+
+ // DELETE FROM indexTable
+ // WHERE lft BETWEEN $left and $right
+ $db = $this->dbh;
+ $q = $db->createDeleteQuery();
+ $q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) )
+ ->where( $q->expr->between( 'lft', $left, $right ) );
+ $s = $q->prepare();
+ $s->execute();
+
+ // Update the left and right values to account for the removed subtree
+ $this->updateNestedValuesForSubtreeDeletion( $right, $width );
+ }
+
+ /**
+ * Moves the node with ID $id as child to the node with ID $targetParentId
+ *
+ * @param string $id
+ * @param string $targetParentId
+ */
+ public function move( $id, $targetParentId )
+ {
+ if ( $this->inTransaction )
+ {
+ $this->addTransactionItem( new ezcTreeTransactionItem(
ezcTreeTransactionItem::MOVE, null, $id, $targetParentId ) );
+ return;
+ }
+
+ $db = $this->dbh;
+
+ // Get the nodes that are gonne be moved in the subtree
+ $ids = array();
+ foreach( $this->fetchSubtreeDepthFirst( $id )->getNodes() as $node )
+ {
+ $ids[] = $node->id;
+ }
+
+ // Update parent ID for the node
+ // UPDATE indexTable
+ // SET parent_id = $targetParentId
+ // WHERE id = $id
+ $q = $db->createUpdateQuery();
+ $q->update( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'parent_id', $q->bindValue( $targetParentId ) )
+ ->where( $q->expr->eq( 'id', $q->bindValue( $id ) ) );
+
+ $s = $q->prepare();
+ $s->execute();
+
+ // Fetch node information
+ list( $origLeft, $origRight, $origWidth ) =
$this->fetchNodeInformation( $id );
+
+ // Update the nested values to account for the moved subtree (delete
part)
+ $this->updateNestedValuesForSubtreeDeletion( $origRight, $origWidth );
+
+ // Fetch node information
+ list( $targetParentLeft, $targetParentRight, $targerParentWidth ) =
$this->fetchNodeInformation( $targetParentId );
+
+ // Update the nested values to account for the moved subtree (addition
part)
+ $this->updateNestedValuesForSubtreeAddition( $targetParentRight,
$origWidth, $ids );
+
+ // Update nodes in moved subtree
+ $adjust = $targetParentRight - $origLeft;
+
+ // UPDATE indexTable
+ // SET rgt = rgt + $adjust
+ // WHERE id in $ids
+ $q = $db->createUpdateQuery();
+ $q->update( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'rgt', $q->expr->add( 'rgt', $adjust ) )
+ ->where( $q->expr->in( 'id', $ids ) );
+ $q->prepare()->execute();
+
+ // UPDATE indexTable
+ // SET lft = lft + $adjust
+ // WHERE id in $ids
+ $q = $db->createUpdateQuery();
+ $q->update( $db->quoteIdentifier( $this->indexTableName ) )
+ ->set( 'lft', $q->expr->add( 'lft', $adjust ) )
+ ->where( $q->expr->in( 'id', $ids ) );
+ $q->prepare()->execute();
+ }
+
+ public function fixateTransaction()
+ {
+ }
+}
+?>
Propchange: trunk/Tree/src/backends/db_nested_set.php
------------------------------------------------------------------------------
svn:eol-style = native
Modified: trunk/Tree/src/tree_autoload.php
==============================================================================
--- trunk/Tree/src/tree_autoload.php [iso-8859-1] (original)
+++ trunk/Tree/src/tree_autoload.php [iso-8859-1] Mon Aug 6 12:00:15 2007
@@ -23,6 +23,7 @@
'ezcTreeDbDataStore' => 'Tree/stores/db.php',
'ezcTreeXmlDataStore' => 'Tree/stores/xml.php',
'ezcTreeDbExternalTableDataStore' =>
'Tree/stores/db_external.php',
+ 'ezcTreeDbNestedSet' =>
'Tree/backends/db_nested_set.php',
'ezcTreeDbParentChild' =>
'Tree/backends/db_parent_child.php',
'ezcTreeMemory' => 'Tree/backends/memory.php',
'ezcTreeMemoryDataStore' => 'Tree/stores/memory.php',
Added: trunk/Tree/tests/db_nested_set_tree.php
==============================================================================
--- trunk/Tree/tests/db_nested_set_tree.php (added)
+++ trunk/Tree/tests/db_nested_set_tree.php [iso-8859-1] Mon Aug 6 12:00:15
2007
@@ -1,0 +1,82 @@
+<?php
+/**
+ * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved.
+ * @license http://ez.no/licenses/new_bsd New BSD License
+ * @version //autogentag//
+ * @filesource
+ * @package Tree
+ * @subpackage Tests
+ */
+
+require_once 'tree.php';
+require_once 'db_tree.php';
+
+/**
+ * @package Tree
+ * @subpackage Tests
+ */
+class ezcTreeDbNestedSetTest extends ezcDbTreeTest
+{
+ private $tempDir;
+
+ protected $tables = array( 'nested_set', 'data', 'datam' );
+ protected $schemaName = 'nested_set.dba';
+
+ public function insertData()
+ {
+ // insert test data
+ $data = array(
+ // child -> parent
+ 1 => array( 'null', 1, 18 ),
+ 2 => array( 1, 2, 3 ),
+ 3 => array( 1, 4, 5 ),
+ 4 => array( 1, 6, 13 ),
+ 6 => array( 4, 7, 12 ),
+ 7 => array( 6, 8, 9 ),
+ 8 => array( 6, 10, 11 ),
+ 5 => array( 1, 14, 17 ),
+ 9 => array( 5, 15, 16 ),
+ );
+ foreach( $data as $childId => $details )
+ {
+ list( $parentId, $left, $right ) = $details;
+ $this->dbh->exec( "INSERT INTO nested_set(id, parent_id, lft, rgt)
VALUES( $childId, $parentId, $left, $right )" );
+ }
+
+ // add data
+ for ( $i = 1; $i <= 8; $i++ )
+ {
+ $this->dbh->exec( "INSERT INTO data(id, data) values ( $i, 'Node
$i' )" );
+ }
+ }
+
+ protected function setUpEmptyTestTree( $dataTable = 'data', $dataField =
'data' )
+ {
+ $store = new ezcTreeDbExternalTableDataStore( $this->dbh, $dataTable,
'id', $dataField );
+ $tree = ezcTreeDbNestedSet::create(
+ $this->dbh,
+ 'nested_set',
+ $store
+ );
+ $this->emptyTables();
+ return $tree;
+ }
+
+ protected function setUpTestTree( $dataTable = 'data', $dataField = 'data'
)
+ {
+ $store = new ezcTreeDbExternalTableDataStore( $this->dbh, $dataTable,
'id', $dataField );
+ $tree = new ezcTreeDbNestedSet(
+ $this->dbh,
+ 'nested_set',
+ $store
+ );
+ return $tree;
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite( "ezcTreeDbNestedSetTest" );
+ }
+}
+
+?>
Propchange: trunk/Tree/tests/db_nested_set_tree.php
------------------------------------------------------------------------------
svn:eol-style = native
Added: trunk/Tree/tests/files/nested_set.dba
==============================================================================
--- trunk/Tree/tests/files/nested_set.dba (added)
+++ trunk/Tree/tests/files/nested_set.dba [iso-8859-1] Mon Aug 6 12:00:15 2007
@@ -1,0 +1,177 @@
+<?php
+return array (
+ 0 =>
+ array (
+ 'nested_set' =>
+ ezcDbSchemaTable::__set_state(array(
+ 'fields' =>
+ array (
+ 'id' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'integer',
+ 'length' => 0,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ 'parent_id' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'integer',
+ 'length' => 0,
+ 'notNull' => false,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ 'lft' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'integer',
+ 'length' => 0,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ 'rgt' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'integer',
+ 'length' => 0,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ ),
+ 'indexes' =>
+ array (
+ 'primary' =>
+ ezcDbSchemaIndex::__set_state(array(
+ 'indexFields' =>
+ array (
+ 'id' =>
+ ezcDbSchemaIndexField::__set_state(array(
+ 'sorting' => NULL,
+ )),
+ ),
+ 'primary' => true,
+ 'unique' => true,
+ )),
+ 'lft' =>
+ ezcDbSchemaIndex::__set_state(array(
+ 'indexFields' =>
+ array (
+ 'lft' =>
+ ezcDbSchemaIndexField::__set_state(array(
+ 'sorting' => NULL,
+ )),
+ ),
+ 'primary' => false,
+ 'unique' => false,
+ )),
+ 'rgt' =>
+ ezcDbSchemaIndex::__set_state(array(
+ 'indexFields' =>
+ array (
+ 'rgt' =>
+ ezcDbSchemaIndexField::__set_state(array(
+ 'sorting' => NULL,
+ )),
+ ),
+ 'primary' => false,
+ 'unique' => false,
+ )),
+ ),
+ )),
+ 'data' =>
+ ezcDbSchemaTable::__set_state(array(
+ 'fields' =>
+ array (
+ 'id' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'integer',
+ 'length' => 0,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ 'data' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'text',
+ 'length' => 255,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ ),
+ 'indexes' =>
+ array (
+ 'primary' =>
+ ezcDbSchemaIndex::__set_state(array(
+ 'indexFields' =>
+ array (
+ 'id' =>
+ ezcDbSchemaIndexField::__set_state(array(
+ 'sorting' => NULL,
+ )),
+ ),
+ 'primary' => true,
+ 'unique' => true,
+ )),
+ ),
+ )),
+ 'datam' =>
+ ezcDbSchemaTable::__set_state(array(
+ 'fields' =>
+ array (
+ 'id' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'integer',
+ 'length' => 0,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ 'name' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'text',
+ 'length' => 255,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ 'born' =>
+ ezcDbSchemaField::__set_state(array(
+ 'type' => 'text',
+ 'length' => 5,
+ 'notNull' => true,
+ 'default' => NULL,
+ 'autoIncrement' => false,
+ 'unsigned' => false,
+ )),
+ ),
+ 'indexes' =>
+ array (
+ 'primary' =>
+ ezcDbSchemaIndex::__set_state(array(
+ 'indexFields' =>
+ array (
+ 'id' =>
+ ezcDbSchemaIndexField::__set_state(array(
+ 'sorting' => NULL,
+ )),
+ ),
+ 'primary' => true,
+ 'unique' => true,
+ )),
+ ),
+ )),
+ ),
+ 1 =>
+ array(
+ ),
+); ?>
Modified: trunk/Tree/tests/suite.php
==============================================================================
--- trunk/Tree/tests/suite.php [iso-8859-1] (original)
+++ trunk/Tree/tests/suite.php [iso-8859-1] Mon Aug 6 12:00:15 2007
@@ -18,6 +18,7 @@
require_once 'memory_tree.php';
require_once 'xml_tree.php';
require_once 'db_parent_child_tree.php';
+require_once 'db_nested_set_tree.php';
/**
* @package Tree
@@ -36,6 +37,7 @@
$this->addTest( ezcTreeMemoryTest::suite() );
$this->addTest( ezcTreeXmlTest::suite() );
$this->addTest( ezcTreeDbParentChildTest::suite() );
+ $this->addTest( ezcTreeDbNestedSetTest::suite() );
}
public static function suite()
Modified: trunk/Tree/tests/tree.php
==============================================================================
--- trunk/Tree/tests/tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/tree.php [iso-8859-1] Mon Aug 6 12:00:15 2007
@@ -498,14 +498,13 @@
self::assertSame( '8', $nodeList['8']->id );
}
- public function testTreeMoveNode()
+ public function testTreeMoveNode1()
{
$tree = $this->setUpTestTree();
self::assertSame( false, $tree->fetchNodeById( 4 )->isChildOf(
$tree->fetchNodeById( 5 ) ) );
$tree->move( '4', '5' ); // makes node 4 a child of node 5
-
self::assertSame( true, $tree->isChildOf( 4, 5 ) );
$nodeList = $tree->fetchNodeById( 8 )->fetchPath();
@@ -518,6 +517,26 @@
self::assertSame( true, $tree->fetchNodeById( 4 )->isSiblingOf(
$tree->fetchNodeById( 9 ) ) );
}
+ public function testTreeMoveNode2()
+ {
+ $tree = $this->setUpTestTree();
+
+ self::assertSame( false, $tree->fetchNodeById( 4 )->isChildOf(
$tree->fetchNodeById( 5 ) ) );
+
+ $tree->move( '4', '9' ); // makes node 4 a child of node 5
+ self::assertSame( true, $tree->isChildOf( 4, 9 ) );
+
+ $nodeList = $tree->fetchNodeById( 8 )->fetchPath();
+ self::assertSame( 6, $nodeList->size );
+ self::assertSame( '1', $nodeList[1]->id );
+ self::assertSame( '5', $nodeList[5]->id );
+ self::assertSame( '9', $nodeList[9]->id );
+ self::assertSame( '4', $nodeList[4]->id );
+ self::assertSame( '6', $nodeList[6]->id );
+ self::assertSame( '8', $nodeList[8]->id );
+ self::assertSame( false, $tree->fetchNodeById( 4 )->isSiblingOf(
$tree->fetchNodeById( 9 ) ) );
+ }
+
public function testTreeMoveNodeWithTransaction()
{
$tree = $this->setUpTestTree();
@@ -534,7 +553,7 @@
self::assertSame( true, $tree->fetchNodeById( 4 )->isSiblingOf(
$tree->fetchNodeById( 9 ) ) );
}
- public function testTreeDeleteNode()
+ public function testTreeDeleteNode1()
{
$tree = $this->setUpTestTree();
@@ -546,12 +565,19 @@
self::assertSame( true, $tree->nodeExists( '4' ) );
self::assertSame( true, $tree->nodeExists( '6' ) );
self::assertSame( true, $tree->nodeExists( '8' ) );
+ }
+
+ public function testTreeDeleteNode2()
+ {
+ $tree = $this->setUpTestTree();
+
+ self::assertSame( true, $tree->nodeExists( '4' ) );
$tree->delete( '4' );
self::assertSame( false, $tree->nodeExists( '4' ) );
self::assertSame( false, $tree->nodeExists( '6' ) );
self::assertSame( false, $tree->nodeExists( '7' ) );
self::assertSame( false, $tree->nodeExists( '8' ) );
- self::assertSame( 2, $tree->fetchNodeById( '1' )->getChildCount() );
+ self::assertSame( 3, $tree->fetchNodeById( '1' )->getChildCount() );
}
public function testTreeDeleteNodeWithTransaction()
@@ -796,7 +822,8 @@
),
'Homo' => array(
'Homo Sapiens' => array(
- 'Homo Sapiens Sapiens'
+ 'Homo Sapiens Sapiens',
+ 'Homo Superior'
),
),
'Pan' => array(
@@ -840,7 +867,7 @@
self::assertSame( true, $tree->nodeExists( 'Homo Sapiens Sapiens' ) );
self::assertSame( true, $tree->isDecendantOf( 'Common Chimpanzee',
'Hominoidea' ) );
self::assertSame( 4, $tree->getChildCount( 'Hominidae' ) );
- self::assertSame( 16, $tree->getChildCountRecursive( 'Hominidae' ) );
+ self::assertSame( 17, $tree->getChildCountRecursive( 'Hominidae' ) );
self::assertSame( true, $tree->isSiblingOf( 'Gorilla', 'Homo' ) );
}
--
svn-components mailing list
[email protected]
http://lists.ez.no/mailman/listinfo/svn-components