? Makefile
? node_cache.test
? node_save_test.patch
? sites/all/modules/devel
? sites/default/files
? sites/default/settings.php
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1043
diff -u -r1.1043 node.module
--- modules/node/node.module	29 Apr 2009 17:48:11 -0000	1.1043
+++ modules/node/node.module	30 Apr 2009 21:39:59 -0000
@@ -788,9 +788,8 @@
  *   An array of node objects indexed by nid.
  */
 function node_load_multiple($nids = array(), $conditions = array(), $reset = FALSE) {
-  static $node_cache = array();
   if ($reset) {
-    $node_cache = array();
+    _node_cache_clear();
   }
   $nodes = array();
 
@@ -807,16 +806,16 @@
   unset($conditions['vid']);
 
   // Load any available nodes from the internal cache.
-  if ($node_cache && !$vid) {
+  if (!$vid) {
     if ($nids) {
-      $nodes += array_intersect_key($node_cache, $passed_nids);
+      $nodes += _node_cache_get($passed_nids);
       // If any nodes were loaded, remove them from the $nids still to load.
       $nids = array_keys(array_diff_key($passed_nids, $nodes));
     }
     // If loading nodes only by conditions, fetch all available nodes from
     // the cache. Nodes which don't match are removed later.
     elseif ($conditions) {
-      $nodes = $node_cache;
+      $nodes = _node_cache_get_all();
     }
   }
 
@@ -914,7 +913,7 @@
     $nodes += $queried_nodes;
     // Add nodes to the cache if we're not loading a revision.
     if (!$vid) {
-      $node_cache += $queried_nodes;
+      _node_cache_set($queried_nodes);
     }
   }
 
@@ -3230,3 +3229,47 @@
 function theme_node_links($element) {
   return theme('links', $element['#value'],  array('class' => 'links inline'));
 }
+
+
+/**
+ * These functions manage the cache of notes
+ */
+
+/* Get nodes from the cache.
+ * Clone them so the cached node isn't polluted
+ */
+function _node_cache_get(array $nids) {
+  global $node_cache;
+
+  $nodes = array();
+  foreach( array_intersect_key($node_cache, array_flip($nids)) as $nid => $node ) {
+    $nodes[] = is_object($node) ? clone($node) : $node;
+  }
+
+  return $nodes;
+}
+
+/* Put nodes into the cache.
+ * Clone them so the cached node isn't polluted
+ */
+function _node_cache_set(array $nodes) {
+  global $node_cache;
+
+  foreach( $nodes as $node ) {
+    $node_cache[$node->nid] = is_object($node) ? clone($node) : $node;
+  }
+}
+
+function _node_cache_get_all() {
+  global $node_cache;
+  return $node_cache;
+}
+
+/* Clear the whole cache */
+function _node_cache_clear() {
+  global $node_cache;
+  $node_cache = array();
+}
+
+/* Initialize the cache */
+_node_cache_clear();
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.22
diff -u -r1.22 node.test
--- modules/node/node.test	25 Apr 2009 17:52:43 -0000	1.22
+++ modules/node/node.test	30 Apr 2009 21:39:59 -0000
@@ -688,7 +688,7 @@
 
   /**
    * Import test, to check if custom node ids are saved properly.
-   * Workflow: 
+   * Workflow:
    *  - first create a piece of content
    *  - save the content
    *  - check if node exists
@@ -697,7 +697,7 @@
     // Node ID must be a number that is not in the database.
     $max_nid = db_result(db_query('SELECT MAX(nid) FROM {node}'));
     $test_nid = $max_nid + mt_rand(1000, 1000000);
-    $title = $this->randomName(8); 
+    $title = $this->randomName(8);
     $node = array(
       'title' => $title,
       'body' => $this->randomName(32),
@@ -715,4 +715,109 @@
     $node_by_title = $this->drupalGetNodeByTitle($title);
     $this->assertTrue($node_by_title, t('Node load by node title.'));
   }
+
+  /**
+   * Test that updating a node via node_save() updates the
+   * cache for that node
+   */
+  function testNodeSaveClearsCache() {
+    $node = new stdClass;
+    $node->type = "article";
+    $node->body = "Basset hounds got long ears";
+    $node->uid  = $this->web_user->uid;
+    
+    /* Save the new node then reload it to make sure its cached */
+    node_save($node);
+    $nid = $node->nid;
+    $node = node_load($node->nid);
+    $this->assertEqual($node->body, "Basset hounds got long ears");
+    $this->assertEqual($node->nid, $nid, "same nid");
+
+    /* Now change it and check node_load() sees the change */
+    $node->body = "Great green globs";
+    node_save($node);
+    $node = node_load($node->nid);
+    $this->assertEqual($node->body, "Great green globs");
+    $this->assertEqual($node->nid, $nid, "same nid");
+  }
 }
+
+
+class NodeCacheTestCase extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('Node cache'),
+      'description' => t("Test node_load()'s cache."),
+      'group' => t('Node'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+    // Create a user that is allowed to post; we'll use this to test the submission.
+    $web_user = $this->drupalCreateUser(array('create article content'));
+    $this->drupalLogin($web_user);
+    $this->web_user = $web_user;
+  }
+
+  /* Test the cache functions work fine when given empty arrays */
+  function testNodeCacheEmpty() {
+    $cache = _node_cache_get_all();
+    $this->assertEqual(count($cache), 0, "Node cache starts empty");
+
+    _node_cache_set(array());
+    $this->assertEqual(count(_node_cache_get_all()), 0, "caching nothing");
+
+    $nodes = _node_cache_get(array());
+    $this->assertEqual(count($nodes), 0, "getting nothing");
+
+    $nodes = _node_cache_get(array(1, 2, 3));
+    $this->assertEqual(count($nodes), 0, "getting nothing w/cache miss");
+  }
+
+
+  function testNodeCache() {
+    $cache = _node_cache_get_all();
+    $this->assertEqual(count($cache), 0, "Node cache starts empty");
+
+
+    /* Make a node and check it gets cached */
+    $node = new stdClass;
+    $node->nid  = 12345;
+    $node->type = "article";
+    $node->body = "Something";
+    $node->uid  = $this->web_user->uid;
+    
+    /* Cache a node and then get it */
+    _node_cache_set(array($node));
+    $cached_nodes = _node_cache_get(array($node->nid));
+    $this->assertEqual(count($cached_nodes), 1);
+
+    /* Make sure that node looks sane */
+    $cached_node = $cached_nodes[0];
+    $this->assertNotNull($cached_node, "got a cached node");
+    $this->assertEqual($cached_node->nid, $node->nid);
+
+    /* Now check they're decoupled */
+    $node->body = "wibble";
+    $this->assertEqual($cached_node->body, "Something", "cached nodes are copies");
+  }
+
+  function testNodeLoadCache() {
+    $node = new stdClass;
+    $node->type = "article";
+    $node->body = "Something";
+    $node->uid  = $this->web_user->uid;
+    
+    /* Save the new node then reload it to make sure its cached */
+    node_save($node);
+    $node = node_load($node->nid);
+    $node->body = "Something else";
+
+    $same_node = node_load($node->nid);
+    $this->assertEqual(
+      $same_node->body, "Something",
+      "Freshly loaded node should not see unsaved node's changes"
+    );
+  }
+}
\ No newline at end of file
