Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.988
diff -u -p -r1.988 node.module
--- modules/node/node.module	13 Oct 2008 00:33:03 -0000	1.988
+++ modules/node/node.module	23 Oct 2008 21:36:49 -0000
@@ -726,93 +726,160 @@ 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 nids.
+ * @param $options
+ *   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_load_multiple($nids = array(), $options = array(), $reset = FALSE) {
+  static $cache = array();
 
   if ($reset) {
-    $nodes = array();
+    foreach ($nids as $nid) {
+      unset($cache[$nid]);
+    }
   }
 
-  $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];
-      }
+  $nodes = array();
+
+  // Load any available nodes from the cache and exclude them from the
+  // database query.
+  foreach ($nids as $nid) {
+    if (isset($cache[$nid])) {
+      $nodes[$nid] = $cache[$nid];
+      unset($nids[$nid]);
     }
-    $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;
+
+  // Remove nodes from the array if they don't match a condition in $options.
+  if (!empty($options)) {
+    foreach ($nodes as $node) {
+      foreach ($options as $key => $value) {
+        if ($node->$key != $value) {
+          unset($nodes[$nid]);
+        }
+      }
     }
-    $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));
+  // Fetch any nodes not statically cached from the database.
+  if (!empty($nids) || !empty($options)) {
+    // Fetch any nodes not statically cached from the database.
+    $query = db_select('node', 'n');
+    $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 = array();
+    $node_fields = drupal_schema_fields_sql('node');
+
+    // vid and title are provided by node_revisions, so remove them.
+    $node_fields = array_diff($node_fields, array('vid', 'title'));
+    foreach ($node_fields as $field) {
+      $query->addField('n', $field, $field);
+    }
+
+    // Add fields from the {node_revisions} table.
+    $node_revision_fields = array();
+    $node_revision_fields = drupal_schema_fields_sql('node_revisions');
+
+    // nid is provided by node, so remove it.
+    $node_revision_fields = array_diff($node_revision_fields, array('nid'));
+
+    foreach ($node_revision_fields as $field) {
+      $query->addField('r', $field, $field);
+    }
+
+    // Add fields from the {users} table.
+    $user_fields = array('name', 'picture', 'data');
+    foreach ($user_fields as $field) {
+      $query->addField('u', $field, $field);
+    }
+
+    if (!empty($nids)) {
+      $query->condition('n.nid', $nids, 'IN');
+    }
+
+    if (!empty($options)) {
+      foreach ($options as $field => $value) {
+        $query->condition('n.' .  $field, $value);
+      }
+    }
+
+    $result = $query->execute();
+
+    foreach ($result AS $node) {
+      $nodes[$node->nid] = $node;
+    }
   }
-  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));
+
+  // Create an array of nodes for each content type and pass this to the
+  // node type specific callback.
+  $types = node_get_types('names');
+  $typed_nodes = array();
+  foreach ($types as $type) {
+    $typed_nodes[$type] = array();
   }
 
-  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;
+  foreach ($nodes as $nid => $value) {
+    foreach ($types as $type) {
+      if ($node->type == $type) {
+        $typed_nodes[$type][$nid] = $value;
       }
     }
+  }
+  $node_types = array();
 
-    if ($extra = node_invoke_nodeapi($node, 'load')) {
-      foreach ($extra as $key => $value) {
-        $node->$key = $value;
+  // Call node type specific callbacks on each typed array of nodes.
+  foreach ($types as $type) {
+    if (!empty($typed_nodes[$type])) {
+      foreach (module_implements('node_type_load') as $module) {
+        $function = $module . '_nodeapi_load';
+        call_user_func_array($function, array($typed_nodes[$type]));
       }
+      $node_types[] = $type;
     }
-    if ($cachable) {
-      $nodes[$node->nid] = is_object($node) ? clone $node : $node;
+    foreach ($typed_nodes[$type] as $nid => $value) {
+      unset($nodes[$nid]);
     }
+    array_merge($nodes, $typed_nodes[$type]);
+  }
+  // Call hook_nodeapi_load(), pass $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';
+    call_user_func_array($function, array(&$nodes,  $node_types));
   }
 
-  return $node;
+  foreach ($nodes as $node) {
+    $cache[$node->nid] = $node;
+  }
+
+  return $nodes;
+}
+
+/**
+ * Load a node object from the database.
+ *
+ * @param $nid
+ *   The node id. 
+ * @param $reset
+ *   Whether to reset the internal node_load cache.
+ *
+ * @return
+ *   A fully-populated node object.
+ */
+function node_load($nid, $reset = FALSE) {
+  $node = node_load_multiple(array($nid), NULL, $reset);
+
+  return $node[$nid];
 }
 
 /**
