Author: dr
Date: Wed Aug  1 15:44:09 2007
New Revision: 5798

Log:
- Implemented deleting data in the stores, so that no stale data exists.
- Fixed issues with retrieving/storing data.
- Allows access (through a read only property) to a tree node's associated
  tree.
- Optimized deleting nodes that have children by fetching the subtree first
  and then running a query to delete the nodes in one go.

Modified:
    trunk/Tree/src/backends/db_parent_child.php
    trunk/Tree/src/backends/memory.php
    trunk/Tree/src/backends/xml.php
    trunk/Tree/src/interfaces/data_store.php
    trunk/Tree/src/stores/db_external.php
    trunk/Tree/src/stores/memory.php
    trunk/Tree/src/stores/xml_internal.php
    trunk/Tree/src/tree_node.php
    trunk/Tree/tests/db_parent_child_tree.php
    trunk/Tree/tests/db_tree.php
    trunk/Tree/tests/files/init.xml
    trunk/Tree/tests/tree.php
    trunk/Tree/tests/tree_node.php
    trunk/Tree/tests/xml_tree.php

Modified: trunk/Tree/src/backends/db_parent_child.php
==============================================================================
--- trunk/Tree/src/backends/db_parent_child.php [iso-8859-1] (original)
+++ trunk/Tree/src/backends/db_parent_child.php [iso-8859-1] Wed Aug  1 
15:44:09 2007
@@ -345,6 +345,7 @@
         $q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) );
         $s = $q->prepare();
         $s->execute();
+        $this->store->deleteDataForAllNodes();
 
         $q = $db->createInsertQuery();
         $q->insertInto( $db->quoteIdentifier( $this->indexTableName ) )
@@ -382,18 +383,15 @@
         $this->store->storeDataForNode( $childNode, $childNode->data );
     }
 
-    private function deleteChildNodes( $nodeId )
+    private function deleteNodes( ezcTreeNodeList $list )
     {
         $db = $this->dbh;
         $q = $db->createDeleteQuery();
-
-        foreach ( $this->fetchChildRecords( $nodeId ) as $record )
-        {
-            $this->deleteChildNodes( $record['id'] );
-        }
-
-        $q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) )
-          ->where( $q->expr->eq( 'id', $q->bindValue( $nodeId ) ) );
+        $q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) );
+
+        $idList = array_keys( $list->getNodes() );
+
+        $q->where( $q->expr->in( 'id', $idList ) );
         $s = $q->prepare();
         $s->execute();
     }
@@ -411,7 +409,9 @@
             return;
         }
 
-        $this->deleteChildNodes( $id );
+        $nodeList = $this->fetchSubtree( $id );
+        $this->deleteNodes( $nodeList );
+        $this->store->deleteDataForNodes( $nodeList );
     }
 
     /**

Modified: trunk/Tree/src/backends/memory.php
==============================================================================
--- trunk/Tree/src/backends/memory.php [iso-8859-1] (original)
+++ trunk/Tree/src/backends/memory.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -347,8 +347,9 @@
      */
     public function setRootNode( ezcTreeNode $node )
     {
-        // wipe nodelist
+        // wipe nodelist and data
         $this->nodeList = array();
+        $this->store->deleteDataForAllNodes();
 
         // replace root node
         $newObj = new ezcTreeMemoryNode( $node, array() );
@@ -401,8 +402,9 @@
         // locate node to move
         $nodeToDelete = $this->getNodeById( $id );
 
-        // fetch the whole subtree
+        // fetch the whole subtree and delete all the associated data
         $children = $nodeToDelete->node->fetchSubtree();
+        $this->store->deleteDataForNodes( $children );
 
         // Use the parent to remove the child
         unset( $nodeToDelete->parent->children[$id] );

Modified: trunk/Tree/src/backends/xml.php
==============================================================================
--- trunk/Tree/src/backends/xml.php [iso-8859-1] (original)
+++ trunk/Tree/src/backends/xml.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -112,7 +112,7 @@
     /**
      * Saves the internal DOM representation of the tree back to disk
      */
-    private function saveFile()
+    public function saveFile()
     {
         $this->dom->save( $this->xmlFile );
     }
@@ -446,6 +446,7 @@
                 $this->delete( $id );
             }
         }
+        $this->store->deleteDataForAllNodes();
 
         // Create new root node
         $root = $this->dom->createElement( 'node' );
@@ -501,20 +502,23 @@
             return;
         }
 
+        // Delete all the associated data
+        $nodeList = $this->fetchSubtree( $id );
+        $this->store->deleteDataForNodes( $nodeList );
+
         // locate node to move
         $nodeToDelete = $this->getNodeById( $id );
 
-        // Use the parent to remove the child
+        // Remove the ID on all children by hand as this would crash in PHP <= 
5.2.3
         $nodeToDelete->removeAttribute( "id" );
 
-        // Remove the ID on all children by hand as this would crash in PHP <= 
5.2.3
         $children = $nodeToDelete->getElementsByTagName( 'node' );
         foreach ( $children as $child )
         {
             $child->removeAttribute( "id" );
         }
 
-        // Remove the node itself
+        // Use the parent to remove the child
         $nodeToDelete->parentNode->removeChild( $nodeToDelete );
 
         if ( !$this->inTransactionCommit )

Modified: trunk/Tree/src/interfaces/data_store.php
==============================================================================
--- trunk/Tree/src/interfaces/data_store.php [iso-8859-1] (original)
+++ trunk/Tree/src/interfaces/data_store.php [iso-8859-1] Wed Aug  1 15:44:09 
2007
@@ -16,6 +16,25 @@
  */
 interface ezcTreeDataStore
 {
+    /**
+     * Deletes the data for the node $node from the data store.
+     *
+     * @param ezcTreeNode $node
+     */
+    public function deleteDataForNode( ezcTreeNode $node );
+
+    /**
+     * Deletes the data for all the nodes in the node list $nodeList.
+     *
+     * @param ezcTreeNodeList $nodeList
+     */
+    public function deleteDataForNodes( ezcTreeNodeList $nodeList );
+
+    /**
+     * Deletes the data for all the nodes in the store.
+     */
+    public function deleteDataForAllNodes();
+
     /**
      * Retrieves the data for the node $node from the data store and assigns it
      * to the node's 'data' property.

Modified: trunk/Tree/src/stores/db_external.php
==============================================================================
--- trunk/Tree/src/stores/db_external.php [iso-8859-1] (original)
+++ trunk/Tree/src/stores/db_external.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -71,6 +71,45 @@
     }
 
     /**
+     * Deletes the data for the node $node from the data store.
+     *
+     * @param ezcTreeNode $node
+     */
+    public function deleteDataForNode( ezcTreeNode $node )
+    {
+    }
+
+    /**
+     * Deletes the data for all the nodes in the node list $nodeList.
+     *
+     * @param ezcTreeNodeList $nodeList
+     */
+    public function deleteDataForNodes( ezcTreeNodeList $nodeList )
+    {
+        $nodeIdsToDelete = array_keys( $nodeList->getNodes() );
+
+        $db = $this->dbHandler;
+        $q = $db->createDeleteQuery();
+        $q->deleteFrom( $db->quoteIdentifier( $this->table ) )
+          ->where( $q->expr->in( $db->quoteIdentifier( $this->idField ), 
$nodeIdsToDelete ) );
+        $s = $q->prepare();
+        $s->execute();
+    }
+
+    /**
+     * Deletes the data for all the nodes in the store.
+     */
+    public function deleteDataForAllNodes()
+    {
+        $db = $this->dbHandler;
+        $q = $db->createDeleteQuery();
+
+        $q->deleteFrom( $db->quoteIdentifier( $this->table ) );
+        $s = $q->prepare();
+        $s->execute();
+    }
+
+    /**
      * Takes the data from the executed query and uses the $dataField 
      * property to filter out the wanted data for this node.
      *
@@ -110,6 +149,7 @@
             throw new ezcTreeDataStoreMissingDataException( $node->id );
         }
         $node->data = $this->filterDataFromResult( $result );
+        $node->dataFetched = true;
     }
 
     /**
@@ -142,6 +182,7 @@
         foreach ( $s as $result )
         {
             $nodeList[$result['id']]->data = $this->filterDataFromResult( 
$result );
+            $nodeList[$result['id']]->dataFetched = true;
         }
     }
 
@@ -191,6 +232,7 @@
         }
         $s = $q->prepare();
         $s->execute();
+        $node->dataStored = true;
     }
 }
 ?>

Modified: trunk/Tree/src/stores/memory.php
==============================================================================
--- trunk/Tree/src/stores/memory.php [iso-8859-1] (original)
+++ trunk/Tree/src/stores/memory.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -16,6 +16,34 @@
  */
 class ezcTreeMemoryDataStore implements ezcTreeDataStore
 {
+    /**
+     * Deletes the data for the node $node from the data store.
+     *
+     * @param ezcTreeNode $node
+     */
+    public function deleteDataForNode( ezcTreeNode $node )
+    {
+        /* This is a no-op as the data is part of the node */
+    }
+
+    /**
+     * Deletes the data for all the nodes in the node list $nodeList.
+     *
+     * @param ezcTreeNodeList $nodeList
+     */
+    public function deleteDataForNodes( ezcTreeNodeList $nodeList )
+    {
+        /* This is a no-op as the data is part of the node */
+    }
+
+    /**
+     * Deletes the data for all the nodes in the store.
+     */
+    public function deleteDataForAllNodes()
+    {
+        /* This is a no-op as the data is part of the node */
+    }
+
     /**
      * Retrieves the data for the node $node from the data store and assigns it
      * to the node's 'data' property.

Modified: trunk/Tree/src/stores/xml_internal.php
==============================================================================
--- trunk/Tree/src/stores/xml_internal.php [iso-8859-1] (original)
+++ trunk/Tree/src/stores/xml_internal.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -18,6 +18,33 @@
 class ezcTreeXmlInternalDataStore extends ezcTreeXmlDataStore
 {
     /**
+     * Deletes the data for the node $node from the data store.
+     *
+     * @param ezcTreeNode $node
+     */
+    public function deleteDataForNode( ezcTreeNode $node )
+    {
+    }
+
+    /**
+     * Deletes the data for all the nodes in the node list $nodeList.
+     *
+     * @param ezcTreeNodeList $nodeList
+     */
+    public function deleteDataForNodes( ezcTreeNodeList $nodeList )
+    {
+        /* This is a no-op as the data is part of the node */
+    }
+
+    /**
+     * Deletes the data for all the nodes in the store.
+     */
+    public function deleteDataForAllNodes()
+    {
+        /* This is a no-op as the data is part of the node */
+    }
+
+    /**
      * Retrieves the data for the node $node from the data store and assigns it
      * to the node's 'data' property.
      *
@@ -27,7 +54,13 @@
     {
         $id = $node->id;
         $elem = $this->dom->getElementById( "id$id" );
-        $node->data = $elem->getElementsByTagNameNS( 
'http://components.ez.no/Tree/data', 'data' )->item(0)->firstChild->data;
+        $dataElem = $elem->getElementsByTagNameNS( 
'http://components.ez.no/Tree/data', 'data' )->item(0);
+        if ( $dataElem === null )
+        {
+            throw new ezcTreeDataStoreMissingDataException( $node->id );
+        }
+        $node->data = $dataElem->firstChild->data;
+        $node->dataFetched = true;
     }
 
     /**
@@ -54,10 +87,22 @@
      */
     public function storeDataForNode( ezcTreeNode $node )
     {
+        // Locate the element
         $id = $node->id;
         $elem = $this->dom->getElementById( "id$id" );
-        $dataNode = $elem->ownerDocument->createElement( 'etd:data', 
$node->data );
+
+        // Locate the data element, and remove it
+        $dataElem = $elem->getElementsByTagNameNS( 
'http://components.ez.no/Tree/data', 'data' )->item(0);
+        if ( $dataElem !== null )
+        {
+            $dataElem->parentNode->removeChild( $dataElem );
+        }
+
+        // Create the new data element and add it
+        $dataNode = $elem->ownerDocument->createElementNS( 
'http://components.ez.no/Tree/data', 'etd:data', $node->data );
         $elem->appendChild( $dataNode );
+        $node->dataStored = true;
+        $node->tree->saveFile();
     }
 }
 ?>

Modified: trunk/Tree/src/tree_node.php
==============================================================================
--- trunk/Tree/src/tree_node.php [iso-8859-1] (original)
+++ trunk/Tree/src/tree_node.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -14,10 +14,15 @@
  * isSiblingOf) are all marshalled to calls on the tree (that is stored in the
  * $tree private variable) itself.
  *
- * @property-read string $id          The ID that uniquely identifies a node
- * @property      mixed  $data        The data belonging to a node
- * @property-read bool   $dataFetched Whether the data for this node has been
- *                                    fetched.
+ * @property-read string  $id          The ID that uniquely identifies a node
+ * @property-read ezcTree $tree        The tree object that this node belongs 
to
+ * @property      mixed   $data        The data belonging to a node
+ * @property      bool    $dataFetched Whether the data for this node has been
+ *                                     fetched. Should *only* be modified by
+ *                                     data store implementations.
+ * @property      bool    $dataStored  Whether the data for this node has been
+ *                                     stored. Should *only* be modified by
+ *                                     data store implementations.
  *
  * @package Tree
  * @version //autogentag//
@@ -33,13 +38,6 @@
     private $properties = array();
 
     /**
-     * Holds a link to the tree that this node is part of
-     *
-     * @var ezcTree
-     */
-    private $tree;
-
-    /**
      * Constructs a new ezcTreeNode object with ID $id on tree $tree
      *
      * @param ezcTree $tree
@@ -48,13 +46,14 @@
      */
     public function __construct( ezcTree $tree, $id )
     {
-        $this->tree = $tree;
         $this->properties['id'] = (string) $id;
+        $this->properties['tree'] = $tree;
 
         if ( func_num_args() === 2 )
         {
             $this->properties['data'] = null;
             $this->properties['dataFetched'] = false;
+            $this->properties['dataStored'] = true;
 
             if ( $tree->prefetch )
             {
@@ -65,6 +64,7 @@
         {
             $this->properties['data'] = func_get_arg( 2 );
             $this->properties['dataFetched'] = true;
+            $this->properties['dataStored'] = false;
         }
     }
 
@@ -89,6 +89,8 @@
                 // break intentionally missing
             case 'id':
             case 'dataFetched':
+            case 'dataStored':
+            case 'tree':
                 return $this->properties[$name];
 
         }
@@ -110,14 +112,28 @@
         switch ( $name )
         {
             case 'id':
-            case 'dataFetched':
+            case 'tree':
                 throw new ezcBasePropertyPermissionException( $name, 
ezcBasePropertyPermissionException::READ );
 
             case 'data':
                 $this->properties[$name] = $value;
+                $this->properties['dataStored'] = false;
+                if ( $this->properties['dataFetched'] )
+                {
+                    $this->tree->store->storeDataForNode( $this );
+                }
                 $this->properties['dataFetched'] = true;
                 return;
 
+            case 'dataFetched':
+            case 'dataStored':
+                if ( !is_bool( $value ) )
+                {
+                    throw new ezcBaseValueException( $name, $value, 'boolean' 
);
+                }
+                $this->properties[$name] = $value;
+                break;
+
             default:
                 throw new ezcBasePropertyNotFoundException( $name );
         }

Modified: trunk/Tree/tests/db_parent_child_tree.php
==============================================================================
--- trunk/Tree/tests/db_parent_child_tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/db_parent_child_tree.php [iso-8859-1] Wed Aug  1 15:44:09 
2007
@@ -184,8 +184,53 @@
         }
     }
 
+    public function testStoreUpdatedData()
+    {
+        $this->emptyTables();
+
+        $store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data', 
'id', 'data' );
+        $tree = ezcTreeDbParentChild::create(
+            $this->dbh,
+            'parent_child',
+            $store
+        );
+
+        $root = $tree->createNode( 1, "Camelinae" );
+        $tree->setRootNode( $root );
+
+        $root->addChild( $tree->createNode( 2, "Lama" ) );
+        $root->addChild( $tree->createNode( 3, "Vicugna" ) );
+        $root->addChild( $tree->createNode( 4, "Camelus" ) );
+
+        // start over
+        $store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data', 
'id', 'data' );
+        $tree = new ezcTreeDbParentChild(
+            $this->dbh,
+            'parent_child',
+            $store
+        );
+
+        $camelus = $tree->fetchNodeById( 4 );
+        self::assertSame( "Camelus", $camelus->data );
+        $camelus->data = "Something Wrong";
+        $camelus->data = "Camels";
+
+        // start over
+        $store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data', 
'id', 'data' );
+        $tree = new ezcTreeDbParentChild(
+            $this->dbh,
+            'parent_child',
+            $store
+        );
+
+        $camelus = $tree->fetchNodeById( 4 );
+        self::assertSame( "Camels", $camelus->data );
+    }
+
     public function testCreateDbTreeWithTransaction()
     {
+        $this->emptyTables();
+
         $store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data', 
'id', 'data' );
         $tree = ezcTreeDbParentChild::create(
             $this->dbh,

Modified: trunk/Tree/tests/db_tree.php
==============================================================================
--- trunk/Tree/tests/db_tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/db_tree.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -51,7 +51,7 @@
             }
 
             // add data
-            for ( $i = 1; $i <= 9; $i++ )
+            for ( $i = 1; $i <= 8; $i++ )
             {
                 $this->dbh->exec( "INSERT INTO data(id, data) values ( $i, 
'Node $i' )" );
             }

Modified: trunk/Tree/tests/files/init.xml
==============================================================================
--- trunk/Tree/tests/files/init.xml [iso-8859-1] (original)
+++ trunk/Tree/tests/files/init.xml [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -23,9 +23,7 @@
         </node>
         <node id="id5">
             <etd:data>Node 5</etd:data>
-            <node id="id9">
-                <etd:data>Node 9</etd:data>
-            </node>
+            <node id="id9"/>
         </node>
     </node>
 </tree>

Modified: trunk/Tree/tests/tree.php
==============================================================================
--- trunk/Tree/tests/tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/tree.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -556,6 +556,19 @@
         self::assertSame( 8, $tree->getChildCountRecursive( '1' ) );
     }
 
+    public function testTreeDeleteNodeAndAddNew()
+    {
+        $tree = $this->setUpTestTree();
+
+        $tree->delete( '5' );
+        self::assertSame( false, $tree->nodeExists( '5' ) );
+        self::assertSame( false, $tree->nodeExists( '9' ) );
+
+        $node1  = $tree->fetchNodeById( 1 );
+        $cervus = $tree->createNode( 9, 'Cervus' );
+        $node1->addChild( $cervus );
+    }
+
     public function testTreeTransactionDoubleStart()
     {
         $tree = $this->setUpTestTree();
@@ -672,6 +685,26 @@
         }
         self::assertSame( true, $tree->nodeExists( '42' ) );
     }
+
+    public function testMissingDataException()
+    {
+        // not applicable from memory store as that always has data
+        if ( get_class( $this ) !== 'ezcTreeMemoryTest' )
+        {
+            $tree = $this->setUpTestTree();
+
+            $node = $tree->fetchNodeById( 9 );
+            try
+            {
+                $data = $node->data;
+                self::fail( "Expected exception not thrown." );
+            }
+            catch ( ezcTreeDataStoreMissingDataException $e )
+            {
+                self::assertSame( "The data store does not have data stored 
for the node with ID '9'.", $e->getMessage() );
+            }
+        }
+    }
 }
 
 ?>

Modified: trunk/Tree/tests/tree_node.php
==============================================================================
--- trunk/Tree/tests/tree_node.php [iso-8859-1] (original)
+++ trunk/Tree/tests/tree_node.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -140,6 +140,56 @@
         self::assertSame( true, $node->dataFetched );
     }
 
+    public function testSetDataFetchedNotBool()
+    {
+        $node = new ezcTreeNode( $this->tree, 'Si', 'Silicon' );
+        
+        try
+        {
+            $node->dataFetched = 42;
+            self::fail( "Expected exception not thrown" );
+        }
+        catch ( ezcBaseValueException $e )
+        {
+            self::assertSame( "The value '42' that you were trying to assign 
to setting 'dataFetched' is invalid. Allowed values are: boolean.", 
$e->getMessage() );
+        }
+    }
+
+    public function testSetDataStoredNotBool()
+    {
+        $node = new ezcTreeNode( $this->tree, 'P', 'Phosphorus' );
+        
+        try
+        {
+            $node->dataStored = "oops!";
+            self::fail( "Expected exception not thrown" );
+        }
+        catch ( ezcBaseValueException $e )
+        {
+            self::assertSame( "The value 'oops!' that you were trying to 
assign to setting 'dataStored' is invalid. Allowed values are: boolean.", 
$e->getMessage() );
+        }
+    }
+
+    public function testSetDataFetched()
+    {
+        $node = new ezcTreeNode( $this->tree, 'S', 'Sulfur' );
+        
+        $node->dataFetched = true;
+        self::assertSame( true, $node->dataFetched );
+        $node->dataFetched = false;
+        self::assertSame( false, $node->dataFetched );
+    }
+
+    public function testSetDataStored()
+    {
+        $node = new ezcTreeNode( $this->tree, 'Cl', 'Chlorine' );
+        
+        $node->dataStored = true;
+        self::assertSame( true, $node->dataStored );
+        $node->dataStored = false;
+        self::assertSame( false, $node->dataStored );
+    }
+
     public static function suite()
     {
          return new PHPUnit_Framework_TestSuite( "ezcTreeNodeTest" );

Modified: trunk/Tree/tests/xml_tree.php
==============================================================================
--- trunk/Tree/tests/xml_tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/xml_tree.php [iso-8859-1] Wed Aug  1 15:44:09 2007
@@ -95,6 +95,41 @@
         );
     }
 
+    public function testStoreUpdatedData()
+    {
+        $tree = ezcTreeXml::create(
+            $this->tempDir . '/new-tree.xml', 
+            new ezcTreeXmlInternalDataStore()
+        );
+
+        $root = $tree->createNode( 1, "Camelinae" );
+        $tree->setRootNode( $root );
+
+        $root->addChild( $tree->createNode( 2, "Lama" ) );
+        $root->addChild( $tree->createNode( 3, "Vicugna" ) );
+        $root->addChild( $tree->createNode( 4, "Camelus" ) );
+
+        // start over
+        $tree = new ezcTreeXml(
+            $this->tempDir . '/new-tree.xml', 
+            new ezcTreeXmlInternalDataStore()
+        );
+
+        $camelus = $tree->fetchNodeById( 4 );
+        self::assertSame( "Camelus", $camelus->data );
+        $camelus->data = "Something Wrong";
+        $camelus->data = "Camels";
+
+        // start over
+        $tree = new ezcTreeXml(
+            $this->tempDir . '/new-tree.xml', 
+            new ezcTreeXmlInternalDataStore()
+        );
+
+        $camelus = $tree->fetchNodeById( 4 );
+        self::assertSame( "Camels", $camelus->data );
+    }
+
     public static function suite()
     {
          return new PHPUnit_Framework_TestSuite( "ezcTreeXmlTest" );


-- 
svn-components mailing list
[email protected]
http://lists.ez.no/mailman/listinfo/svn-components

Reply via email to