Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.473
diff -u -p -r1.473 book.module
--- modules/book/book.module	29 Oct 2008 10:08:51 -0000	1.473
+++ modules/book/book.module	9 Nov 2008 02:56:01 -0000
@@ -682,16 +682,13 @@ function book_build_active_trail($book_l
 /**
  * Implementation of hook_nodeapi_load().
  */
-function book_nodeapi_load(&$node, $teaser, $page) {
-  // Note - we cannot use book_link_load() because it will call node_load().
-  $info['book'] = db_fetch_array(db_query('SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid = %d', $node->nid));
-
-  if ($info['book']) {
-    $info['book']['href'] = $info['book']['link_path'];
-    $info['book']['title'] = $info['book']['link_title'];
-    $info['book']['options'] = unserialize($info['book']['options']);
-
-    return $info;
+function book_nodeapi_load($nodes) {
+  $result = db_query("SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid IN (" . db_placeholders(array_keys($nodes)) . ")",  array_keys($nodes), array('fetch' => PDO::FETCH_ASSOC));
+  foreach ($result as $record) {
+    $nodes[$record['nid']]->book = $record;
+    $nodes[$record['nid']]->book['href'] = $record['link_path'];
+    $nodes[$record['nid']]->book['title'] = $record['link_title'];
+    $nodes[$record['nid']]->book['options'] = unserialize($record['options']);
   }
 }
 
Index: modules/book/book.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.test,v
retrieving revision 1.3
diff -u -p -r1.3 book.test
--- modules/book/book.test	15 May 2008 21:19:24 -0000	1.3
+++ modules/book/book.test	9 Nov 2008 02:56:01 -0000
@@ -152,7 +152,7 @@ class BookTestCase extends DrupalWebTest
     }
 
     // Check to make sure the book node was created.
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertNotNull(($node === FALSE ? NULL : $node), t('Book node found in database.'));
     $number++;
 
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.661
diff -u -p -r1.661 comment.module
--- modules/comment/comment.module	8 Nov 2008 20:43:54 -0000	1.661
+++ modules/comment/comment.module	9 Nov 2008 02:56:08 -0000
@@ -566,11 +566,28 @@ function comment_form_alter(&$form, $for
 /**
  * Implementation of hook_nodeapi_load().
  */
-function comment_nodeapi_load(&$node, $arg = 0) {
-  if ($node->comment != COMMENT_NODE_DISABLED) {
-    return db_fetch_array(db_query("SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = %d", $node->nid));
+function comment_nodeapi_load($nodes) {
+  $comments_enabled = array();
+
+  // If comments are disabled, add some default values.
+  foreach ($nodes as $node) {
+    $node->last_comment_timestamp = $nodes[$node->nid]->created;
+    $node->last_comment_name = '';
+    $node->comment_count = 0;
+    if ($node->comment !=  COMMENT_NODE_DISABLED) {
+      $comments_enabled[] = $node->nid;
+    }
+  }
+
+  // If comments are enabled, fetch information from the database.
+  if (!empty($comments_enabled)) {
+    $result = db_query('SELECT nid, last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid IN(' . db_placeholders(array_keys($nodes)) . ')', array_keys($nodes));
+    foreach ($result as $record) {
+      $nodes[$record->nid]->last_comment_timestamp = $record->last_comment_timestamp;
+      $nodes[$record->nid]->last_comment_name = $record->last_comment_name;
+      $nodes[$record->nid]->comment_count = $record->comment_count;
+    }
   }
-  return array('last_comment_timestamp' => $node->created, 'last_comment_name' => '', 'comment_count' => 0);
 }
 
 /**
Index: modules/dblog/dblog.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/dblog/dblog.test,v
retrieving revision 1.9
diff -u -p -r1.9 dblog.test
--- modules/dblog/dblog.test	17 Sep 2008 07:11:56 -0000	1.9
+++ modules/dblog/dblog.test	9 Nov 2008 02:56:09 -0000
@@ -265,7 +265,7 @@ class DBLogTestCase extends DrupalWebTes
     $this->drupalPost('node/add/' . $type, $edit, t('Save'));
     $this->assertResponse(200);
     // Retrieve node object.
-    $node = node_load(array('title' => $title));
+    $node = $this->drupalGetNodeByTitle($title);
     $this->assertTrue($node != null, t('Node @title was loaded', array('@title' => $title)));
     // Edit node.
     $edit = $this->getContentUpdate($type);
Index: modules/filter/filter.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v
retrieving revision 1.8
diff -u -p -r1.8 filter.test
--- modules/filter/filter.test	12 Oct 2008 04:30:06 -0000	1.8
+++ modules/filter/filter.test	9 Nov 2008 02:56:10 -0000
@@ -117,7 +117,7 @@ class FilterAdminTestCase extends Drupal
     $this->drupalPost('node/add/page', $edit, t('Save'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.'));
 
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue($node, t('Node found in database.'));
 
     $this->drupalGet('node/' . $node->nid);
Index: modules/forum/forum.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.module,v
retrieving revision 1.472
diff -u -p -r1.472 forum.module
--- modules/forum/forum.module	2 Nov 2008 14:42:45 -0000	1.472
+++ modules/forum/forum.module	9 Nov 2008 02:56:13 -0000
@@ -341,11 +341,25 @@ function forum_nodeapi_delete(&$node, $t
 /**
  * Implementation of hook_nodeapi_load().
  */
-function forum_nodeapi_load(&$node, $teaser, $page) {
+function forum_nodeapi_load($nodes, $types) {
   $vid = variable_get('forum_nav_vocabulary', '');
-  $vocabulary = taxonomy_vocabulary_load($vid);
-  if (_forum_nodeapi_check_node_type($node, $vocabulary)) {
-    return db_fetch_array(db_query('SELECT tid AS forum_tid FROM {forum} WHERE vid = %d', $node->vid));
+  // If no forum vocabulary is set up, return.
+  if (!$vocabulary = taxonomy_vocabulary_load($vid)) {
+    return;
+  }
+
+  $vids = array();
+  foreach ($nodes as $node) {
+    if (isset($vocabulary->nodes[$node->type])) {
+      $vids[] = $node->vid;
+      $node->forum = array();
+    }
+  }
+  if (!empty($vids)) {
+    $result = db_query('SELECT nid, tid  FROM {forum} WHERE vid IN(' . db_placeholders($vids) . ')', $vids);
+    foreach ($result as $record) {
+      $nodes[$record->nid]->forum[] = $record->tid;
+    }
   }
 }
 
@@ -452,15 +466,6 @@ function forum_form_alter(&$form, $form_
 }
 
 /**
- * Implementation of hook_load().
- */
-function forum_load($node) {
-  $forum = db_fetch_object(db_query('SELECT * FROM {term_node} WHERE vid = %d', $node->vid));
-
-  return $forum;
-}
-
-/**
  * Implementation of hook_block().
  *
  * Generates a block containing the currently active forum topics and the
Index: modules/forum/forum.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.test,v
retrieving revision 1.6
diff -u -p -r1.6 forum.test
--- modules/forum/forum.test	5 Nov 2008 12:47:23 -0000	1.6
+++ modules/forum/forum.test	9 Nov 2008 02:56:14 -0000
@@ -240,7 +240,7 @@ class ForumTestCase extends DrupalWebTes
     }
 
     // Retrieve node object.
-    $node = node_load(array('title' => $title), null, true); // Are these last two parameters necessary?
+    $node = $this->drupalGetNodeByTitle($title);
     $this->assertTrue($node != null, t('Node @title was loaded', array('@title' => $title)));
 
     // View forum topic.
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.994
diff -u -p -r1.994 node.module
--- modules/node/node.module	8 Nov 2008 20:43:54 -0000	1.994
+++ modules/node/node.module	9 Nov 2008 02:56:21 -0000
@@ -730,93 +730,168 @@ function node_invoke_nodeapi(&$node, $op
 }
 
 /**
- * Load a node object from the database.
+ * Load node objects from the database.
  *
- * @param $param
- *   Either the nid of the node or an array of conditions to match against in the database query
- * @param $revision
- *   Which numbered revision to load. Defaults to the current version.
+ * @param $nids
+ *   An array of node IDs.
+ * @param $conditions
+ *   An array of conditions on the {node} table in the form 'field' => $value.
  * @param $reset
  *   Whether to reset the internal node_load cache.
  *
  * @return
- *   A fully-populated node object.
+ *   An array of node objects indexed by nid.
  */
-function node_load($param = array(), $revision = NULL, $reset = NULL) {
-  static $nodes = array();
-
+function node_multiple_load($nids = array(), $conditions = array(), $reset = FALSE) {
+  static $node_cache = array();
   if ($reset) {
-    $nodes = array();
+    $node_cache = array();
   }
+  $nodes = array();
 
-  $cachable = ($revision == NULL);
-  $arguments = array();
-  if (is_numeric($param)) {
-    if ($cachable) {
-      // Is the node statically cached?
-      if (isset($nodes[$param])) {
-        return is_object($nodes[$param]) ? clone $nodes[$param] : $nodes[$param];
+  // Revisions are not statically cached, and require a different query to
+  // other conditions, so separate vid into its own variable.
+  $vid = isset($conditions['vid']) ? $conditions['vid'] : FALSE;
+  unset($conditions['vid']);
+
+  // Load any available nodes from the internal cache and remove them from
+  // the nodes to be loaded. Store whether $nids was passed so that all
+  // nodes can be returned as soon as possible if loaded from the cache.
+  $nids_was_passed = $nids ? TRUE : FALSE;
+  if ($nids && !$vid) {
+    foreach ($nids as $key => $nid) {
+      if (isset($node_cache[$nid])) {
+        $nodes[$nid] = $node_cache[$nid];
+        unset($nids[$key]);
       }
     }
-    $cond = 'n.nid = %d';
-    $arguments[] = $param;
   }
-  elseif (is_array($param)) {
-    // Turn the conditions into a query.
-    foreach ($param as $key => $value) {
-      $cond[] = 'n.' . db_escape_table($key) . " = '%s'";
-      $arguments[] = $value;
+
+  // If there are conditions to query against, load nodes from the cache
+  // where possible, then remove any which don't match.
+  if ($conditions && $node_cache && !$vid) {
+    // Since nids and conditions are ANDed together in the database
+    // query, only load nodes from cache if no nids were passed.
+    if (!$nids_was_passed) {
+      $nodes = $node_cache;
+    }
+
+    // Conditions are ANDed together, so exclude nodes that fail to match
+    // any individual condition.
+    foreach ($nodes as $node) {
+      foreach ($conditions as $key => $value) {
+        if ($node->$key != $value) {
+          unset($node);
+        }
+      }
     }
-    $cond = implode(' AND ', $cond);
-  }
-  else {
-    return FALSE;
   }
 
-  // Retrieve a field list based on the site's schema.
-  $fields = drupal_schema_fields_sql('node', 'n');
-  $fields = array_merge($fields, drupal_schema_fields_sql('node_revisions', 'r'));
-  $fields = array_merge($fields, array('u.name', 'u.picture', 'u.data'));
-  // Remove fields not needed in the query: n.vid and r.nid are redundant,
-  // n.title is unnecessary because the node title comes from the
-  // node_revisions table.  We'll keep r.vid, r.title, and n.nid.
-  $fields = array_diff($fields, array('n.vid', 'n.title', 'r.nid'));
-  $fields = implode(', ', $fields);
-  // Rename timestamp field for clarity.
-  $fields = str_replace('r.timestamp', 'r.timestamp AS revision_timestamp', $fields);
-  // Change name of revision uid so it doesn't conflict with n.uid.
-  $fields = str_replace('r.uid', 'r.uid AS revision_uid', $fields);
-
-  // Retrieve the node.
-  // No db_rewrite_sql is applied so as to get complete indexing for search.
-  if ($revision) {
-    array_unshift($arguments, $revision);
-    $node = db_fetch_object(db_query('SELECT ' . $fields . ' FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d WHERE ' . $cond, $arguments));
+  // If all required nodes have been loaded from the cache, return. We can
+  // only do this if $nids was passed as a parameter, since returning nodes
+  // based on conditions can only be  guaranteed by checking the database.
+  if ($nids_was_passed && $nodes && !$nids) {
+    return $nodes;
   }
   else {
-    $node = db_fetch_object(db_query('SELECT ' . $fields . ' FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE ' . $cond, $arguments));
-  }
+    // Fetch any nodes not statically cached from the database.
+    $query = db_select('node', 'n');
 
-  if ($node && $node->nid) {
-    // Call the node specific callback (if any) and piggy-back the
-    // results to the node or overwrite some values.
-    if ($extra = node_invoke($node, 'load')) {
-      foreach ($extra as $key => $value) {
-        $node->$key = $value;
-      }
+    if ($vid) {
+      $query->join('node_revisions', 'r', 'r.nid = n.nid AND r.vid = :vid', array(':vid' => $vid));
+    }
+    else {
+      $query->join('node_revisions', 'r', 'r.vid = n.vid');
     }
+    $query->join('users', 'u', 'u.uid = n.uid');
+
+    // Add fields from the {node} table.
+    $node_fields = drupal_schema_fields_sql('node');
+
+    // vid and title are provided by node_revisions, so remove them.
+    unset($node_fields['vid']);
+    unset($node_fields['title']);
+    $query->fields('n', $node_fields);
+
+    // Add all fields from the {node_revisions} table.
+    $node_revision_fields = drupal_schema_fields_sql('node_revisions');
+
+    // nid is provided by node, so remove it.
+    unset($node_revision_fields['nid']);
+
+    // Change timestamp to revisions_timestamp before adding it to the query.
+    unset($node_revision_fields['timestamp']);
+    $query->addField('r', 'timestamp', 'revision_timestamp');
+    $query->fields('r', $node_revision_fields);
+
+    // Add all fields from the {users} table.
+    $user_fields = array('name', 'picture', 'data');
+    $query->fields('u', $user_fields);
 
-    if ($extra = node_invoke_nodeapi($node, 'load')) {
-      foreach ($extra as $key => $value) {
-        $node->$key = $value;
+    if ($nids) {
+      $query->condition('n.nid', $nids, 'IN');
+    }
+    if ($conditions) {
+      foreach ($conditions as $field => $value) {
+        $query->condition('n.' . $field, $value);
       }
     }
-    if ($cachable) {
-      $nodes[$node->nid] = is_object($node) ? clone $node : $node;
+    $nodes += $query->execute()->fetchAllAssoc('nid');
+  }
+
+  // If we've got to this point and not loaded any nodes, return FALSE.
+  if (!$nodes) {
+    return FALSE;
+  }
+
+  // Create an array of nodes for each content type and pass this to the
+  // node type specific callback.
+  $typed_nodes = array();
+  foreach ($nodes as $nid => $node) {
+    $typed_nodes[$node->type][$nid] = $node;
+  }
+
+  // Call node type specific callbacks on each typed array of nodes.
+  foreach ($typed_nodes as $type => $nodes_of_type) {
+    if (node_hook($type, 'load')) {
+      $function = node_get_types('base', $type) . '_load';
+      $function($nodes_of_type);
     }
   }
 
-  return $node;
+  // Call hook_nodeapi_load(), pass the node types so modules can return early
+  // if not acting on types in the array.
+  foreach (module_implements('nodeapi_load') as $module) {
+    $function = $module . '_nodeapi_load';
+    $function($nodes, array_keys($typed_nodes));
+  }
+  if (!$vid) {
+    $node_cache += $nodes;
+  }
+  return $nodes;
+}
+
+/**
+ * Load a node object from the database.
+ *
+ * @param $nid
+ *   The node ID.
+ * @param $vid
+ *   The revision ID.
+ * @param $reset
+ *   Whether to reset the internal node_load cache.
+ *
+ * @return
+ *   A fully-populated node object.
+ */
+function node_load($nid, $vid = NULL, $reset = FALSE) {
+  $vid = isset($vid) ? array('vid' => $vid) : NULL;
+  if ($node = node_multiple_load(array($nid), $vid, $reset)) {
+    return $node[$nid];
+  }
+  else {
+    return FALSE;
+  }
 }
 
 /**
@@ -1826,15 +1901,18 @@ function node_feed($nids = FALSE, $chann
  */
 function node_page_default() {
   $result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10));
-
-  $output = '';
-  $num_rows = FALSE;
-  while ($node = db_fetch_object($result)) {
-    $output .= node_view(node_load($node->nid), 1);
-    $num_rows = TRUE;
+  $nids = array();
+  foreach ($result as $record) {
+    $nids[] = $record->nid;
   }
-
+  $num_rows = !empty($nids) ? TRUE : FALSE;
   if ($num_rows) {
+    $nodes = node_multiple_load($nids);
+    $output = '';
+    foreach ($nids as $nid) {
+      $output .= node_view($nodes[$nid], TRUE);
+    }
+
     $feed_url = url('rss.xml', array('absolute' => TRUE));
     drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') . ' ' . t('RSS'));
     $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
Index: modules/node/node.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.test,v
retrieving revision 1.7
diff -u -p -r1.7 node.test
--- modules/node/node.test	11 Oct 2008 18:29:20 -0000	1.7
+++ modules/node/node.test	9 Nov 2008 02:56:22 -0000
@@ -1,6 +1,81 @@
 <?php
 // $Id: node.test,v 1.7 2008/10/11 18:29:20 webchick Exp $
 
+class NodeMultipleLoadTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Node multiple load'),
+      'description' => t('Test the loading of multiple nodes.'),
+      'group' => t('Node'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    parent::setUp();
+    $web_user = $this->drupalCreateUser(array('create article content', 'create page content'));
+    $this->drupalLogin($web_user);
+  }
+
+  /**
+   * Create four nodes and ensure they're loaded correctly.
+   */
+  function testNodeMultipleLoad() {
+    $node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+    $node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
+    $node3 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 0));
+    $node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0));
+
+    // Confirm that promoted nodes appear in the default node listing.
+    $this->drupalGet('node');
+    $this->assertText($node1->title, t('Node title appears on the default listing.'));
+    $this->assertText($node2->title, t('Node title appears on the default listing.'));
+    $this->assertNoText($node3->title, t('Node title does not appear in the default listing.'));
+    $this->assertNoText($node4->title, t('Node title does not appear in the default listing.'));
+
+    // Load nodes where promote = 0.
+    $nodes = node_multiple_load(NULL, array('promote' => 0));
+    $this->assertEqual($node3->title, $nodes[$node3->nid]->title, t('Node was loaded.'));
+    $this->assertEqual($node4->title, $nodes[$node4->nid]->title, t('Node was loaded.'));
+    $this->assertTrue(count($nodes) == 2, t('Two nodes loaded.'));
+
+    // Load multiple nodes by nid.
+    $nids = array($node1->nid, $node2->nid, $node3->nid, $node4->nid);
+    $nodes = node_multiple_load($nids);
+    $this->assertTrue(count($nodes) == 4, t('Four nodes loaded'));
+    foreach ($nodes as $node) {
+      $this->assertTrue(is_object($node), t('Node is an object'));
+    }
+
+    // Load nodes by nid, where type = article.
+    $nodes = node_multiple_load($nids, array('type' => 'article'));
+    $this->assertTrue(count($nodes) == 3);
+    $this->assertEqual($nodes[$node1->nid]->title, $node1->title, t('Node successfully loaded.'));
+    $this->assertEqual($nodes[$node2->nid]->title, $node2->title, t('Node successfully loaded.'));
+    $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded.'));
+    $this->assertFalse(isset($nodes[$node4->nid]));
+
+    // Now that the nodes have been loaded into the static cache, ensure that
+    // are loaded correctly again when a condition is passed.
+    $nodes = node_multiple_load($nids, array('type' => 'article'));
+    $this->assertTrue(count($nodes) == 3, t('Three nodes loaded.'));
+    $this->assertEqual($nodes[$node1->nid]->title, $node1->title, t('Node successfully loaded'));
+    $this->assertEqual($nodes[$node2->nid]->title, $node2->title, t('Node successfully loaded'));
+    $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded'));
+    $this->assertFalse(isset($nodes[$node4->nid]), t('Node was not loaded'));
+
+    // Load nodes by nid, where type = article and promote = 0.
+    $nodes = node_multiple_load($nids, array('type' => 'article', 'promote' => 0));
+    $this->assertTrue(count($nodes) == 1, t('One node loaded'));
+    $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded.'));
+  }
+}
+
 class NodeRevisionsTestCase extends DrupalWebTestCase {
   protected $nodes;
   protected $logs;
@@ -273,7 +348,7 @@ class PageEditTestCase extends DrupalWeb
     $this->drupalPost('node/add/page', $edit, t('Save'));
 
     // Check that the node exists in the database.
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue($node, t('Node found in database.'));
 
     // Check that "edit" link points to correct page.
@@ -379,7 +454,7 @@ class PageCreationTestCase extends Drupa
     $this->assertRaw(t('!post %title has been created.', array('!post' => 'Page', '%title' => $edit['title'])), t('Page created.'));
 
     // Check that the node exists in the database.
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue($node, t('Node found in database.'));
   }
 }
Index: modules/path/path.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.module,v
retrieving revision 1.149
diff -u -p -r1.149 path.module
--- modules/path/path.module	12 Oct 2008 04:30:06 -0000	1.149
+++ modules/path/path.module	9 Nov 2008 02:56:22 -0000
@@ -135,12 +135,14 @@ function path_nodeapi_validate(&$node, $
 /**
  * Implementation of hook_nodeapi_load().
  */
-function path_nodeapi_load(&$node, $arg) {
-  $language = isset($node->language) ? $node->language : '';
-  $path = 'node/' . $node->nid;
-  $alias = drupal_get_path_alias($path, $language);
-  if ($path != $alias) {
-    $node->path = $alias;
+function path_nodeapi_load($nodes) {
+  foreach($nodes as $node) {
+    $language = isset($node->language) ? $node->language : '';
+    $path = 'node/' . $node->nid;
+    $alias = drupal_get_path_alias($path, $language);
+    if ($path != $alias) {
+      $node->path = $alias;
+    }
   }
 }
 
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.4
diff -u -p -r1.4 path.test
--- modules/path/path.test	13 Oct 2008 20:57:19 -0000	1.4
+++ modules/path/path.test	9 Nov 2008 02:56:24 -0000
@@ -131,7 +131,7 @@ class PathTestCase extends DrupalWebTest
     $this->drupalPost('node/add/page', $edit, t('Save'));
 
     // Check to make sure the node was created.
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
 
     $this->assertNotNull(($node === FALSE ? NULL : $node), 'Node found in database. %s');
 
@@ -188,7 +188,7 @@ class PathLanguageTestCase extends Drupa
     $this->drupalPost('node/add/page', $edit, t('Save'));
 
     // Check to make sure the node was created.
-    $english_node = node_load(array('title' => $edit['title']));
+    $english_node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue(($english_node), 'Node found in database.');
 
     // Confirm that the alias works.
@@ -209,7 +209,7 @@ class PathLanguageTestCase extends Drupa
 
     // Ensure the node was created.
     // Check to make sure the node was created.
-    $french_node = node_load(array('title' => $edit['title']));
+    $french_node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue(($french_node), 'Node found in database.');
 
     // Confirm that the alias works.
Index: modules/poll/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.277
diff -u -p -r1.277 poll.module
--- modules/poll/poll.module	12 Oct 2008 04:30:07 -0000	1.277
+++ modules/poll/poll.module	9 Nov 2008 02:56:25 -0000
@@ -148,11 +148,10 @@ function poll_block($op = 'list', $delta
     }
     elseif ($op == 'view') {
       // Retrieve the latest poll.
-      $sql = db_rewrite_sql("SELECT MAX(n.created) FROM {node} n INNER JOIN {poll} p ON p.nid = n.nid WHERE n.status = 1 AND p.active = 1");
-      $timestamp = db_result(db_query($sql));
-      if ($timestamp) {
-        $poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'status' => 1));
-
+      $sql = db_rewrite_sql("SELECT MAX(n.created), n.nid FROM {node} n INNER JOIN {poll} p ON p.nid = n.nid WHERE n.status = 1 AND p.active = 1 GROUP BY n.nid");
+      $record = db_query($sql)->fetch();
+      if ($record) {
+        $poll = node_load($record->nid);
         if ($poll->nid) {
           $poll = poll_view($poll, TRUE, FALSE, TRUE);
         }
@@ -451,35 +450,38 @@ function poll_validate($node) {
 /**
  * Implementation of hook_load().
  */
-function poll_load($node) {
+function poll_load($nodes) {
   global $user;
+  foreach ($nodes as $node) {
+    $poll = db_query("SELECT runtime, active FROM {poll} WHERE nid = :nid", array(':nid' => $node->nid))->fetch();
 
-  $poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
-
-  // Load the appropriate choices into the $poll object.
-  $result = db_query("SELECT chid, chtext, chvotes, weight FROM {poll_choices} WHERE nid = %d ORDER BY weight", $node->nid);
-  while ($choice = db_fetch_array($result)) {
-    $poll->choice[$choice['chid']] = $choice;
-  }
-
-  // Determine whether or not this user is allowed to vote.
-  $poll->allowvotes = FALSE;
-  if (user_access('vote on polls') && $poll->active) {
-    if ($user->uid) {
-      $result = db_fetch_object(db_query('SELECT chid FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
+    // Load the appropriate choices into the $poll object.
+    $result = db_query("SELECT chid, chtext, chvotes, weight FROM {poll_choices} WHERE nid = :nid ORDER BY weight", array(':nid' => $node->nid), array('fetch' => PDO::FETCH_ASSOC));
+    foreach ($result as $record) {
+      $poll->choice[$record['chid']] = $record;
     }
-    else {
-      $result = db_fetch_object(db_query("SELECT chid FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, ip_address()));
-    }
-    if (isset($result->chid)) {
-      $poll->vote = $result->chid;
-    }
-    else {
-      $poll->vote = -1;
-      $poll->allowvotes = TRUE;
+
+    // Determine whether or not this user is allowed to vote.
+    $poll->allowvotes = FALSE;
+    if (user_access('vote on polls') && $poll->active) {
+      if ($user->uid) {
+        $result = db_query('SELECT chid FROM {poll_votes} WHERE nid = :nid AND uid = :uid', array(':nid' => $node->nid, ':uid' => $user->uid))->fetch();
+      }
+      else {
+        $result = db_query("SELECT chid FROM {poll_votes} WHERE nid = :nid AND hostname = :hostname", array(':nid' => $node->nid, ':hostname' => ip_address()))->fetch;
+      }
+      if (isset($result->chid)) {
+        $poll->vote = $result->chid;
+      }
+      else {
+        $poll->vote = -1;
+        $poll->allowvotes = TRUE;
+      }
     }
+  foreach ($poll as $key => $value) {
+    $nodes[$node->nid]->$key = $value;
+  }
   }
-  return $poll;
 }
 
 /**
Index: modules/poll/poll.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.test,v
retrieving revision 1.5
diff -u -p -r1.5 poll.test
--- modules/poll/poll.test	5 Aug 2008 18:07:14 -0000	1.5
+++ modules/poll/poll.test	9 Nov 2008 02:56:25 -0000
@@ -40,7 +40,7 @@ class PollTestCase extends DrupalWebTest
     }
 
     $this->drupalPost(NULL, $edit, t('Save'));
-    $node = node_load(array('title' => $title));
+    $node = $this->drupalGetNodeByTitle($title);
     $this->assertRaw(t('@type %title has been created.', array('@type' => node_get_types('name', 'poll'), '%title' => $title)), 'Poll has been created.');
     $this->assertTrue($node->nid, t('Poll has been found in the database'));
 
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.55
diff -u -p -r1.55 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	5 Nov 2008 17:06:18 -0000	1.55
+++ modules/simpletest/drupal_web_test_case.php	9 Nov 2008 02:56:29 -0000
@@ -357,6 +357,23 @@ class DrupalWebTestCase {
   }
 
   /**
+   * Get a node from the database based on its title.
+   *
+   * @param title
+   *   A node title, usually generated by $this->randomName().
+   *
+   * @return
+   *   A node object matching $title.
+   */
+  function drupalGetNodeByTitle($title) {
+    $nodes = node_multiple_load(NULL, array('title' => $title));
+    foreach ($nodes as $node) {
+      $returned_node = $node;
+    }
+    return $returned_node;
+  }
+
+  /**
    * Creates a node based on default settings.
    *
    * @param $settings
Index: modules/simpletest/tests/taxonomy_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/taxonomy_test.module,v
retrieving revision 1.2
diff -u -p -r1.2 taxonomy_test.module
--- modules/simpletest/tests/taxonomy_test.module	2 Nov 2008 17:46:47 -0000	1.2
+++ modules/simpletest/tests/taxonomy_test.module	9 Nov 2008 02:56:30 -0000
@@ -9,8 +9,10 @@
 /**
  * Implementation of hook_taxonomy_term_load().
  */
-function taxonomy_test_taxonomy_term_load($term) {
-  $term->antonyms = taxonomy_test_get_antonyms($term->tid);
+function taxonomy_test_taxonomy_term_load(&$terms) {
+  foreach ($terms as $term) {
+    $term->antonyms = taxonomy_test_get_antonyms($term->tid);
+  }
 }
 
 /**
Index: modules/system/system.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.test,v
retrieving revision 1.23
diff -u -p -r1.23 system.test
--- modules/system/system.test	1 Nov 2008 21:21:35 -0000	1.23
+++ modules/system/system.test	9 Nov 2008 02:56:31 -0000
@@ -526,7 +526,7 @@ class PageTitleFiltering extends DrupalW
     // Create the node with HTML in the title.
     $this->drupalPost('node/add/page', $edit, t('Save'));
 
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertNotNull($node, 'Node created and found in database');
     $this->drupalGet("node/" . $node->nid);
     $this->assertText(check_plain($edit['title']), 'Check to make sure tags in the node title are converted.');
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.439
diff -u -p -r1.439 taxonomy.module
--- modules/taxonomy/taxonomy.module	7 Nov 2008 18:52:18 -0000	1.439
+++ modules/taxonomy/taxonomy.module	9 Nov 2008 02:56:36 -0000
@@ -621,6 +621,33 @@ function taxonomy_node_get_terms_by_voca
 }
 
 /**
+ * Find all term IDs associated with a set of nodes.
+ *
+ * @param $nodes
+ *  An array of node objects.
+ *
+ * @return
+ *  An array of term and node IDs ordered by vocabulary and term weight.
+ */
+function taxonomy_get_tids_from_nodes($nodes) {
+  $vids = array();
+  foreach ($nodes as $node) {
+    $vids[] = $node->vid;
+  }
+  $query = db_select('term_node', 'r');
+  $query->fields('r', array('tid', 'nid', 'vid'));
+  $query->join('term_data', 't', 'r.tid = t.tid');
+  $query->join('vocabulary', 'v', 't.vid = v.vid');
+  $query->condition('r.vid', $vids, 'IN');
+  $query->orderBy('v.weight');
+  $query->orderBy('t.weight');
+  $query->orderBy('t.name');
+  $query->addTag('term_access');
+
+  return $query->execute()->fetchAllAssoc('tid');
+}
+
+/**
  * Find all terms associated with the given node, ordered by vocabulary and term weight.
  */
 function taxonomy_node_get_terms($node, $key = 'tid') {
@@ -1047,24 +1074,113 @@ function taxonomy_terms_load($str_tids) 
 }
 
 /**
+ * Load multiple taxonomy terms based on certain conditions.
+ *
+ * @param $tids
+ *  An array of taxonomy term IDs.
+ * @param $conditions
+ *  An array of conditions to add to the query.
+ * @param $reset
+ *  Whether to reset the internal cache.
+ *
+ * @return
+ *  An array of term objects, indexed by tid.
+ */
+function taxonomy_term_multiple_load($tids = array(), $conditions = array(), $reset = FALSE) {
+  static $term_cache = array();
+
+  if ($reset) {
+    $term_cache = array();
+  }
+
+  $terms = array();
+
+  // Load any available terms from the cache. We need to know if $tids was
+  // passed when loading tids from conditions.
+  $tids_was_passed = $tids ? TRUE : FALSE;
+  if ($tids) {
+    foreach ($tids as $key => $tid) {
+      if (isset($term_cache[$tid])) {
+        $terms[$tid] = $term_cache[$tid];
+        unset($tids[$key]);
+      }
+    }
+  }
+
+  // If there are any conditions to query against, load terms from the cache
+  // where possible and remove any already loaded which don't match.
+  if ($conditions && $term_cache) {
+    // Since tids and conditions are ANDed together in the query, only load
+    // terms from cache if no tids were passed.
+    if (!$tids_was_passed) {
+      $terms = $term_cache;
+    }
+    foreach ($terms as $tid => $term) {
+      foreach ($conditions as $key => $value) {
+        if ($term->$key != $value) {
+          unset($term);
+        }
+      }
+    }
+  }
+
+  // If all required terms have been loaded from the cache, return. This can
+  // only be guaranteed if $tids was passed in.
+  if ($tids_was_passed && !empty($terms) && empty($tids)) {
+    return $terms;
+  }
+  else {
+    // Load any remaining terms from the database.
+    $query = db_select('term_data', 't');
+    $term_data = drupal_schema_fields_sql('term_data');
+    $query->fields('t', $term_data);
+
+    // If the $tids array is populated, add those to the query.
+    if ($tids) {
+      $query->condition('t.tid', $tids, 'IN');
+    }
+
+    // If the conditions array is populated, add those to the query.
+    if ($conditions) {
+      foreach ($conditions as $field => $value) {
+        $query->conditions('t.' . $field, $value);
+      }
+    }
+    $terms += $query->execute()->fetchAllAssoc('tid');
+  }
+
+  // If no terms have been loaded, return FALSE.
+  if (!$terms) {
+    return FALSE;
+  }
+
+  // Invoke hook_taxonomy_term_load() on the terms array.
+  foreach (module_implements('taxonomy_term_load') as $module) {
+    $function = $module . '_taxonomy_term_load';
+    $function($terms);
+  }
+  $term_cache += $terms;
+
+  return $terms;
+}
+
+/**
  * Return the term object matching a term ID.
  *
  * @param $tid
  *   A term's ID
+ * @param $reset
+ *   Whether to reset the static cache.
  *
- * @return Object
+ * @return
  *   A term object. Results are statically cached.
  */
 function taxonomy_term_load($tid, $reset = FALSE) {
   if (!is_numeric($tid)) {
     return FALSE;
   }
-  static $terms = array();
-  if (!isset($terms[$tid]) || $reset) {
-    $terms[$tid] = taxonomy_get_term_data($tid, $reset);
-    module_invoke_all('taxonomy_term_load', $terms[$tid]);
-  }
-  return $terms[$tid];
+  $terms = taxonomy_term_multiple_load(array($tid), NULL, $reset);
+  return $terms ? $terms[$tid] : FALSE;
 }
 
 /**
@@ -1191,11 +1307,17 @@ function taxonomy_select_nodes($tids = a
 function taxonomy_render_nodes($result) {
   $output = '';
   $has_rows = FALSE;
-  while ($node = db_fetch_object($result)) {
-    $output .= node_view(node_load($node->nid), 1);
-    $has_rows = TRUE;
+  $nids = array();
+  foreach ($result as $record) {
+    $nids[] = $record->nid;
   }
+  $has_rows = !empty($nids) ? TRUE : FALSE;
   if ($has_rows) {
+    $nodes = node_multiple_load($nids);
+
+    foreach ($nodes as $node) {
+      $output .= node_view($node, 1);
+    }
     $output .= theme('pager', NULL, variable_get('default_nodes_main', 10), 0);
   }
   else {
@@ -1207,9 +1329,21 @@ function taxonomy_render_nodes($result) 
 /**
  * Implementation of hook_nodeapi_load().
  */
-function taxonomy_nodeapi_load($node, $arg = 0) {
-  $output['taxonomy'] = taxonomy_node_get_terms($node);
-  return $output;
+function taxonomy_nodeapi_load($nodes) {
+  // Get an array of tids ordered by vocabulary and term weight.
+  $tids = taxonomy_get_tids_from_nodes($nodes);
+  // Load the full term objects for these tids.
+  $terms = taxonomy_term_multiple_load(array_keys($tids));
+  foreach ($tids as $term) {
+    if ($nodes[$term->nid]->vid == $term->vid) {
+      $nodes[$term->nid]->taxonomy[$term->tid] = $terms[$term->tid];
+    }
+  }
+  foreach ($nodes as $node) {
+    if (!isset($nodes[$node->nid]->taxonomy)) {
+      $node->taxonomy = array();
+    }
+  }
 }
 
 /**
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.10
diff -u -p -r1.10 taxonomy.test
--- modules/taxonomy/taxonomy.test	5 Nov 2008 14:08:11 -0000	1.10
+++ modules/taxonomy/taxonomy.test	9 Nov 2008 02:56:38 -0000
@@ -71,6 +71,77 @@ class TaxonomyVocabularyLoadTestCase ext
   }
 }
 
+/**
+ * Test the taxonomy_term_multiple_load() function.
+ */
+
+class TaxonomyMultipleLoadTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Taxonomy term multiple loading'),
+      'description' => t('Test the loading of multiple taxonomy terms at once'),
+      'group' => t('Taxonomy'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+   function setUp() {
+     parent::setUp();
+     $this->taxonomy_admin = $this->drupalCreateUser(array('administer taxonomy'));
+     $this->drupalLogin($this->taxonomy_admin);
+   }
+
+  /**
+   * Create a vocabulary and some taxonomy terms, ensuring they're loaded
+   * correctly using taxonomy_term_multiple_load().
+   */
+  function testTaxonomyTermMultipleLoad() {
+    // Create a vocabulary.
+    $vocabulary = new stdClass();
+    $vocabulary->name = $this->randomName();
+    $vocabulary->help = '';
+    taxonomy_vocabulary_save($vocabulary);
+
+    // Create five terms in the vocabulary.
+    $i = 0;
+    while ($i < 5) {
+      $i++;
+      $edit = array();
+      $edit['name'] = $this->randomName();
+      $this->drupalPost('admin/content/taxonomy/' . $vocabulary->vid . '/add', $edit, t('Save'));
+    }
+    // Load the terms from the vocabulary.
+    $terms = taxonomy_term_multiple_load(NULL, array('vid' => $vocabulary->vid));
+    $count = count($terms);
+    $this->assertTrue($count == 5, t('Correct number of terms were loaded. !count terms.', array('!count' => $count)));
+
+    // Load the same terms again by tid.
+    $terms2 = taxonomy_term_multiple_load(array_keys($terms));
+    $this->assertTrue($count == count($terms2), t('Five terms were loaded by tid'));
+    $this->assertEqual($terms, $terms2, t('Both arrays contain the same terms'));
+
+    // Load the terms by tid, with a condition on vid.
+    $terms3 = taxonomy_term_multiple_load(array_keys($terms2), array('vid' => $vocabulary->vid));
+    $this->assertEqual($terms2, $terms3);
+
+    // Remove one term from the array, then delete it.
+    $deleted = array_shift($terms3);
+    taxonomy_term_delete($deleted->tid);
+    $deleted_term = taxonomy_term_load($deleted->tid, TRUE);
+    $this->assertFalse($deleted_term);
+
+    // Load terms from the vocabulary by vid.
+    $terms4 = taxonomy_term_multiple_load(NULL, array('vid' => $vocabulary->vid), TRUE);
+    $this->assertTrue(count($terms4 == 4), t('Correct number of terms were loaded.'));
+    $this->assertFalse(isset($terms4[$deleted->tid]));
+  }
+}
+
 class TaxonomyVocabularyFunctionsTestCase extends DrupalWebTestCase {
   /**
    * Implementation of getInfo().
@@ -476,7 +547,7 @@ class TaxonomyTestNodeApiTestCase extend
     $patternArray['body text'] = $body;
     $patternArray['title'] = $title;
 
-    $node = node_load(array('title' => $title));
+    $node = $this->drupalGetNodeByTitle($title);
 
     $this->drupalGet("node/$node->nid");
     foreach($patternArray as $name => $termPattern) {
@@ -521,7 +592,7 @@ class TaxonomyTestNodeApiTestCase extend
     }
 
     // Checking database fields.
-    $node = node_load(array('title' => $title));
+    $node = $this->drupalGetNodeByTitle($title);
     $result = db_query('SELECT tid FROM {term_node} WHERE vid = %d', $node->vid);
     while ($nodeRow = db_fetch_array($result)) {
       $this->assertTrue(in_array($nodeRow['tid'], $parent), t('Checking database field.'));
Index: modules/translation/translation.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/translation/translation.test,v
retrieving revision 1.3
diff -u -p -r1.3 translation.test
--- modules/translation/translation.test	30 May 2008 07:30:53 -0000	1.3
+++ modules/translation/translation.test	9 Nov 2008 02:56:39 -0000
@@ -119,7 +119,7 @@ class TranslationTestCase extends Drupal
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Page created.'));
 
     // Check to make sure the node was created.
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue($node, t('Node found in database.'));
 
     return $node;
@@ -143,7 +143,7 @@ class TranslationTestCase extends Drupal
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Translation created.'));
 
     // Check to make sure that translation was successfull.
-    $node = node_load(array('title' => $edit['title']));
+    $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue($node, t('Node found in database.'));
 
     return $node;
Index: modules/trigger/trigger.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v
retrieving revision 1.3
diff -u -p -r1.3 trigger.test
--- modules/trigger/trigger.test	2 Jun 2008 17:39:12 -0000	1.3
+++ modules/trigger/trigger.test	9 Nov 2008 02:56:40 -0000
@@ -47,7 +47,7 @@ class TriggerContentTestCase extends Dru
       // Make sure the text we want appears.
       $this->assertRaw(t('!post %title has been created.', array ('!post' => 'Page', '%title' => $edit['title'])), t('Make sure the page has actually been created'));
       // Action should have been fired.
-      $loaded_node = node_load(array('title' => $edit['title']), NULL, TRUE);
+      $loaded_node = $this->drupalGetNodeByTitle($edit['title']);;
       $this->assertTrue($loaded_node->$info['property'] == $info['expected'], t('Make sure the @action action fired.', array('@action' => $info['name'])));
       // Leave action assigned for next test
 
@@ -111,4 +111,4 @@ class TriggerContentTestCase extends Dru
     );
     return $info[$action];
   }
-}
\ No newline at end of file
+}
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.215
diff -u -p -r1.215 upload.module
--- modules/upload/upload.module	8 Nov 2008 21:35:10 -0000	1.215
+++ modules/upload/upload.module	9 Nov 2008 02:56:41 -0000
@@ -300,10 +300,11 @@ function upload_file_delete(&$file) {
 /**
  * Implementation of hook_nodeapi_load().
  */
-function upload_nodeapi_load(&$node, $teaser) {
-  if (variable_get("upload_$node->type", 1) == 1) {
-    $output = array('files' => upload_load($node));
-    return $output;
+function upload_nodeapi_load($nodes, $types) {
+  foreach ($nodes as $node) {
+    if (variable_get("upload_$node->type", 1) == 1) {
+      $node->files = upload_load($node);
+    }
   }
 }
 
