Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.454
diff -u -p -r1.454 taxonomy.module
--- modules/taxonomy/taxonomy.module	14 Jan 2009 21:16:20 -0000	1.454
+++ modules/taxonomy/taxonomy.module	17 Jan 2009 01:08:48 -0000
@@ -800,14 +800,13 @@ function taxonomy_get_children($tid, $vi
  *
  * @param $vid
  *   Which vocabulary to generate the tree for.
- *
  * @param $parent
  *   The term ID under which to generate the tree. If 0, generate the tree
  *   for the entire vocabulary.
- *
  * @param $max_depth
  *   The number of levels of the tree to return. Leave NULL to return all levels.
- *
+ * @param $reset
+ *   Whether to reset the static cache.
  * @param $depth
  *   Internal use only.
  *
@@ -816,9 +815,13 @@ function taxonomy_get_children($tid, $vi
  *   to have "depth" and "parents" attributes in addition to its normal ones.
  *   Results are statically cached.
  */
-function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $depth = -1) {
+function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $reset = FALSE, $depth = -1) {
   static $children, $parents, $terms;
 
+  if ($reset) {
+    $children = $parents = $terms = array();
+  }
+
   $depth++;
 
   // We cache trees, so it's not CPU-intensive to call get_tree() on a term
@@ -847,7 +850,7 @@ function taxonomy_get_tree($vid, $parent
         $tree[] = $term;
 
         if (!empty($children[$vid][$child])) {
-          $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $max_depth, $depth));
+          $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $max_depth, $reset, $depth));
         }
       }
     }
@@ -900,61 +903,47 @@ function taxonomy_get_synonym_root($syno
  * Count the number of published nodes classified by a term.
  *
  * @param $tid
- *   The term's ID
- *
+ *   The term ID
  * @param $type
- *   The $node->type. If given, taxonomy_term_count_nodes only counts
+ *   (Optional) The $node->type. If given, taxonomy_term_count_nodes only counts
  *   nodes of $type that are classified with the term $tid.
+ * @param $reset
+ *   (Optional) Boolean to indicated whether to reset the internal cache.
  *
- * @return int
+ * @return
  *   An integer representing a number of nodes.
  *   Results are statically cached.
  */
-function taxonomy_term_count_nodes($tid, $type = 0) {
-  static $count;
-
-  if (!isset($count[$type])) {
-    // $type == 0 always evaluates TRUE if $type is a string
-    if (is_numeric($type)) {
-      $result = db_query(db_rewrite_sql('SELECT t.tid, COUNT(n.nid) AS c FROM {taxonomy_term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 GROUP BY t.tid'));
-    }
-    else {
-      $result = db_query(db_rewrite_sql("SELECT t.tid, COUNT(n.nid) AS c FROM {taxonomy_term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 AND n.type = '%s' GROUP BY t.tid"), $type);
-    }
-    $count[$type] = array();
-    while ($term = db_fetch_object($result)) {
-      $count[$type][$term->tid] = $term->c;
-    }
-  }
-  $children_count = 0;
-  foreach (_taxonomy_term_children($tid) as $c) {
-    $children_count += taxonomy_term_count_nodes($c, $type);
+function taxonomy_term_count_nodes($tid, $type = NULL, $reset = FALSE) {
+  static $count = array();
+  if ($reset) {
+    $count = array();
   }
-  return $children_count + (isset($count[$type][$tid]) ? $count[$type][$tid] : 0);
-}
 
-/**
- * Helper for taxonomy_term_count_nodes(). Used to find out
- * which terms are children of a parent term.
- *
- * @param $tid
- *   The parent term's ID
- *
- * @return array
- *   An array of term IDs representing the children of $tid.
- *   Results are statically cached.
- *
- */
-function _taxonomy_term_children($tid) {
-  static $children;
+  // If $type is NULL, change it to 0 to allow it to be used as an aray key
+  // for the static cache.
+  $type = empty($type) ? 0 : $type;
 
-  if (!isset($children)) {
-    $result = db_query('SELECT tid, parent FROM {taxonomy_term_hierarchy}');
-    while ($term = db_fetch_object($result)) {
-      $children[$term->parent][] = $term->tid;
+  if (!isset($count[$type][$tid])) {
+    $term = taxonomy_term_load($tid);
+    $tree = taxonomy_get_tree($term->vid, $tid, NULL, $reset);
+    $tids = array($tid);
+    foreach ($tree as $descendent) {
+      $tids[] = $descendent->tid;
+    }
+
+    $query = db_select('taxonomy_term_node', 't');
+    $query->addExpression('COUNT(DISTINCT(n.nid))', 'nid_count');
+    $query->join('node', 'n', 't.vid = n.vid');
+    $query->condition('t.tid', $tids, 'IN');
+    $query->condition('n.status', 1);
+    if (!is_numeric($type)) {
+      $query->condition('n.type', $type);
     }
+    $query->addTag('term_access');
+    $count[$type][$tid] = $query->execute()->fetchField();
   }
-  return isset($children[$tid]) ? $children[$tid] : array();
+  return $count[$type][$tid];
 }
 
 /**
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.21
diff -u -p -r1.21 taxonomy.test
--- modules/taxonomy/taxonomy.test	14 Jan 2009 21:16:21 -0000	1.21
+++ modules/taxonomy/taxonomy.test	17 Jan 2009 01:08:49 -0000
@@ -237,6 +237,78 @@ function getInfo() {
 }
 
 /**
+ * Unit tests for taxonomy term functions.
+ */
+class TaxonomyTermUnitTest extends TaxonomyWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('Taxonomy term unit tests'),
+      'description' => t('Unit tests for taxonomy term functions.'),
+      'group' => t('Taxonomy'),
+    );
+  }
+
+  /**
+   * Tests for taxonomy_term_count_nodes().
+   *
+   * Attach nodes to a hierarchical vocabulary and check they are counted
+   * correctly.
+   */
+  function testTaxonomyTermCountNodes() {
+    // Create a vocabulary with three terms.
+    $vocabulary = $this->createVocabulary();
+    $term1 = $this->createTerm($vocabulary->vid);
+    $term2 = $this->createTerm($vocabulary->vid);
+    $term3 = $this->createTerm($vocabulary->vid);
+
+    // Attach term1 to a node.
+    $node1 = $this->drupalCreateNode(array('type' => 'page'));
+    $node1->taxonomy = array($term1->tid);
+    node_save($node1);
+    $this->assertEqual(taxonomy_term_count_nodes($term1->tid), 1, t('Term has one valid node association.'));
+
+    // Attach term2 to a node.
+    $node2 = $this->drupalCreateNode(array('type' => 'article'));
+    $node2->taxonomy = array($term2->tid);
+    node_save($node2);
+    $this->assertEqual(taxonomy_term_count_nodes($term2->tid), 1, t('Term has one valid node association.'));
+
+    // Confirm that term3 is not associated with any nodes.
+    //$this->assertEqual(taxonomy_term_count_nodes($term3->tid), 0, t('Term is not associated with any nodes'));
+
+    // Set term3 as the parent of term1.
+    $term1->parent = array($term3->tid);
+    taxonomy_term_save($term1);
+
+    // Confirm that the term hierarchy is altered correctly.
+    $children = taxonomy_get_children($term3->tid);
+    $this->assertTrue(isset($children[$term1->tid]), t('Term 3 saved as parent of term 1'));
+    $this->assertEqual(count(taxonomy_get_tree($term3->vid, $term3->tid, NULL, TRUE)), 1, t('Term 3 has one child term'));
+
+    // Confirm that term3's parental relationship with term1 leads to a
+    // node assocation being counted.
+    $this->assertEqual(taxonomy_term_count_nodes($term3->tid, 0, TRUE), 1, t('Term has one valid node association due to child term.'));
+
+    // Set term3 as the parent of term2.
+    $term2->parent = array($term3->tid);
+    taxonomy_term_save($term2);
+
+    // term3 should now have two node associations counted.
+    $this->assertEqual(taxonomy_term_count_nodes($term3->tid, 0, TRUE), 2, t('Term has two valid node associations due to child terms.'));
+
+    // Save node1 with both child taxonomy terms, this should still result
+    // in term3 having two node associations.
+    $node1->taxonomy = array($term1->tid, $term2->tid);
+    node_save($node1);
+    $this->assertEqual(taxonomy_term_count_nodes($term3->tid, 0, TRUE), 2, t('Term has two valid node associations.'));
+
+    // Confirm that the node type argument returns a single node association.
+    $this->assertEqual(taxonomy_term_count_nodes($term3->tid, 'page', TRUE), 1, t("Term is associated with one node of type 'page'."));
+  }
+}
+
+/**
  * Tests for taxonomy term functions.
  */
 class TaxonomyTermTestCase extends TaxonomyWebTestCase {
