Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.146
diff -u -p -r1.146 file.inc
--- includes/file.inc	4 Dec 2008 11:09:33 -0000	1.146
+++ includes/file.inc	19 Dec 2008 00:51:05 -0000
@@ -267,55 +267,116 @@ function file_check_location($source, $d
 }
 
 /**
- * Load a file object from the database.
+ * Load file objects from the database.
  *
- * @param $param
- *   Either the id of a file or an array of conditions to match against in the
- *   database query.
+ * @param $fids
+ *   An array of file IDs.
+ * @param $conditions
+ *   An array of conditions to match against in the database query.
  * @param $reset
- *   Whether to reset the internal file_load cache.
+ *   Whether to reset the internal cache.
+ *
  * @return
- *   A file object.
+ *  An array of file objects, indexed by fid.
  *
  * @see hook_file_load()
  */
-function file_load($param, $reset = NULL) {
-  static $files = array();
+function file_load_multiple($fids = array(), $conditions = array(), $reset = FALSE) {
+  static $file_cache = array();
 
   if ($reset) {
-    $files = array();
+    $file_cache = array();
   }
 
-  if (is_numeric($param)) {
-    if (isset($files[(string) $param])) {
-      return is_object($files[$param]) ? clone $files[$param] : $files[$param];
+  $files = array();
+
+  // Create a new variable which is either a prepared version of the $fids
+  // array for later comparison with the file cache, or FALSE if no $fids were
+  // passed. The $fids array is reduced as items are loaded from cache, and we
+  // need to know if it's empty for this reason to avoid querying the database
+  // when all requested files are loaded from cache.
+  $passed_fids = !empty($fids) ? array_flip($fids) : FALSE;
+
+  // Load any available files from the internal cache.
+  if ($file_cache) {
+    if ($fids) {
+      $files += array_intersect_key($file_cache, $passed_fids);
+      // If any files were loaded, remove them from the $fids still to load.
+      $fids = array_keys(array_diff_key($passed_fids, $files));
+    }
+    // If only conditions is passed, load all files from the cache. Files
+    // which don't match conditions will be removed later.
+    elseif ($conditions) {
+      $files = $file_cache;
+    }
+  }
+  // Remove any loaded files from the array if they don't match $conditions.
+  if ($conditions) {
+    foreach ($files as $file) {
+      $file_values = (array) $file;
+      if (array_diff_assoc($conditions, $file_values)) {
+        unset($files[$file->fid]);
+      }
     }
-    $result = db_query('SELECT f.* FROM {files} f WHERE f.fid = :fid', array(':fid' => $param));
   }
-  elseif (is_array($param)) {
-    // Turn the conditions into a query.
-    $cond = array();
-    $arguments = array();
-    foreach ($param as $key => $value) {
-      $cond[] = 'f.' . db_escape_table($key) . " = '%s'";
-      $arguments[] = $value;
+
+  // Load any remaining files from the database, this is necessary if we have
+  // $fids still to load, or if $conditions was passed without $fids.
+  if ($fids || ($conditions && !$passed_fids)) {
+    $query = db_select('files', 'f');
+    $file_fields = drupal_schema_fields_sql('files');
+    $query->fields('f', $file_fields);
+
+    // If the $fids array is populated, add those to the query.
+    if ($fids) {
+      $query->condition('f.fid', $fids, 'IN');
+    }
+
+    // If the conditions array is populated, add those to the query.
+    if ($conditions) {
+      foreach ($conditions as $field => $value) {
+        $query->conditions('f.' . $field, $value);
+      }
+    }
+    $queried_files = $query->execute()->fetchAllAssoc('fid');
+    // Invoke hook_file_load() on the terms loaded from the database
+    // and add them to the static cache.
+    if (!empty($queried_files)) {
+      foreach (module_implements('file_load') as $module) {
+        $function = $module . '_file_load';
+        $function($queried_files);
+      }
+      $files += $queried_files;
+      $file_cache += $queried_files;
     }
-    $result = db_query('SELECT f.* FROM {files} f WHERE ' . implode(' AND ', $cond), $arguments);
-  }
-  else {
-    return FALSE;
   }
-  $file = $result->fetch(PDO::FETCH_OBJ);
 
-  if ($file && $file->fid) {
-    // Allow modules to add or change the file object.
-    module_invoke_all('file_load', $file);
-
-    // Cache the fully loaded value.
-    $files[(string) $file->fid] = clone $file;
+  // Ensure that the returned array is ordered the same as the original $fids
+  // array if this was passed in and remove any invalid fids.
+  if ($passed_fids) {
+    // Remove any invalid fids from the array.
+    $passed_fids = array_intersect_key($passed_fids, $files);
+    foreach ($files as $file) {
+      $passed_fids[$file->fid] = $file;
+    }
+    $files = $passed_fids;
   }
+  return $files;
+}
 
-  return $file;
+/**
+ * Load a file object from the database.
+ *
+ * @param $fid
+ *  A file ID.
+ * @param $reset
+ *   Whether to reset the internal file_load cache.
+ * @return
+ *   A file object.
+ */
+function file_load($fid, $reset = FALSE) {
+  $files = file_load_multiple(array($fid), array(), $reset);
+  return reset($files);
 }
 
 /**
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.14
diff -u -p -r1.14 file.test
--- modules/simpletest/tests/file.test	27 Nov 2008 08:41:45 -0000	1.14
+++ modules/simpletest/tests/file.test	19 Dec 2008 00:51:06 -0000
@@ -338,7 +338,7 @@ class FileSaveUploadTest extends FileHoo
    * Test the file_save_upload() function.
    */
   function testFileSaveUpload() {
-    $max_fid_before = db_result(db_query('SELECT MAX(fid) AS fid FROM {files}'));
+    $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField();
     $upload_user = $this->drupalCreateUser(array('access content'));
     $this->drupalLogin($upload_user);
 
@@ -354,7 +354,23 @@ class FileSaveUploadTest extends FileHoo
 
     $max_fid_after = db_result(db_query('SELECT MAX(fid) AS fid FROM {files}'));
     $this->assertTrue($max_fid_after > $max_fid_before, t('A new file was created.'));
-    $this->assertTrue(file_load($max_fid_after), t('Loaded the file.'));
+    $file1 = file_load($max_fid_after);
+    $this->assertTrue($file1, t('Loaded the file.'));
+
+    // Upload a second file.
+    $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField();
+    $image2 = current($this->drupalGetTestFiles('image'));
+    $edit = array('files[file_test_upload]' => realpath($image2->filename));
+    $this->drupalPost('file-test/upload', $edit, t('Submit'));
+    $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField();
+
+    $file2 = file_load($max_fid_after);
+    $this->assertTrue($file2);
+
+    // Load both files using file_load_multiple().
+    $files = file_load_multiple(array($file1->fid, $file2->fid));
+    $this->assertTrue(isset($files[$file1->fid]), t('File was loaded successfully'));
+    $this->assertTrue(isset($files[$file2->fid]), t('File was loaded successfully'));
   }
 }
 
@@ -781,7 +797,7 @@ class FileDeleteTest extends FileHookTes
     $this->assertFileHookCalled('references');
     $this->assertFileHookCalled('delete');
     $this->assertFalse(file_exists($file->filepath), t("Test file has actually been deleted."));
-    $this->assertFalse(file_load(array('filepath' => $file->filepath)), t("File was removed from the database."));
+    $this->assertFalse(reset(file_load_multiple(array(), array('filepath' => $file->filepath))), t("File was removed from the database."));
 
     // TODO: implement hook_file_references() in file_test.module and report a
     // file in use and test the $force parameter.
@@ -884,7 +900,7 @@ class FileLoadTest extends FileHookTestC
    * Try to load a non-existant file by filepath.
    */
   function testLoadMissingFilepath() {
-    $this->assertFalse(file_load(array('filepath' => 'misc/druplicon.png')), t("Try to load a file that doesn't exist in the database fails."));
+    $this->assertFalse(reset(file_load_multiple(array(), array('filepath' => 'misc/druplicon.png'))), t("Try to load a file that doesn't exist in the database fails."));
     $this->assertFileHookCalled('load', 0);
   }
 
@@ -892,7 +908,7 @@ class FileLoadTest extends FileHookTestC
    * Try to load a non-existant file by status.
    */
   function testLoadInvalidStatus() {
-    $this->assertFalse(file_load(array('status' => -99)), t("Trying to load a file with an invalid status fails."));
+    $this->assertFalse(reset(file_load_multiple(array(), array('status' => -99))), t("Trying to load a file with an invalid status fails."));
     $this->assertFileHookCalled('load', 0);
   }
 
@@ -913,7 +929,7 @@ class FileLoadTest extends FileHookTestC
 
     // Load by path.
     file_test_reset();
-    $by_path_file = file_load(array('filepath' => $file->filepath));
+    $by_path_file = reset(file_load_multiple(array(), array('filepath' => $file->filepath)));
     $this->assertFileHookCalled('load');
     $this->assertTrue($by_path_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.'));
     $this->assertEqual($by_path_file->fid, $file->fid, t("Loading by filepath got the correct fid."), 'File');
@@ -1061,4 +1077,4 @@ class FileSaveDataTest extends FileHookT
     $file = file_save_data($contents, 'asdf.txt', FILE_EXISTS_ERROR);
     $this->assertFalse($file, t("Overwriting a file fails when FILE_EXISTS_ERROR is specified."));
   }
-}
\ No newline at end of file
+}
Index: modules/simpletest/tests/file_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file_test.module,v
retrieving revision 1.4
diff -u -p -r1.4 file_test.module
--- modules/simpletest/tests/file_test.module	8 Nov 2008 21:35:09 -0000	1.4
+++ modules/simpletest/tests/file_test.module	19 Dec 2008 00:51:06 -0000
@@ -148,11 +148,13 @@ function file_test_set_return($op, $valu
 /**
  * Implementation of hook_file_load().
  */
-function file_test_file_load($file) {
-  _file_test_log_call('load', array($file));
-  // Assign a value on the object so that we can test that the $file is passed
-  // by reference.
-  $file->file_test['loaded'] = TRUE;
+function file_test_file_load($files) {
+  foreach ($files as $file) {
+    _file_test_log_call('load', array($file));
+    // Assign a value on the object so that we can test that the $file is passed
+    // by reference.
+    $file->file_test['loaded'] = TRUE;
+  }
 }
 
 /**
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.4
diff -u -p -r1.4 system.api.php
--- modules/system/system.api.php	16 Dec 2008 22:05:51 -0000	1.4
+++ modules/system/system.api.php	19 Dec 2008 00:51:07 -0000
@@ -981,23 +981,24 @@ function custom_url_rewrite_inbound(&$re
 }
 
 /**
- * Load additional information into a file object.
+ * Load additional information into file objects.
  *
- * file_load() calls this hook to allow modules to load additional information
- * into the $file.
+ * file_load_multiple() calls this hook to allow modules to load
+ * additional information into each file.
  *
- * @param $file
- *   The file object being loaded.
- * @return
- *   None.
+ * @param $files
+ *   An array of file objects, indexed by fid.
  *
- * @see file_load()
+ * @see file_load_multiple()
+ * @see upload_file_load()
  */
-function hook_file_load(&$file) {
+function hook_file_load($files) {
   // Add the upload specific data into the file object.
-  $values = db_query('SELECT * FROM {upload} u WHERE u.fid = :fid', array(':fid' => $file->fid))->fetch(PDO::FETCH_ASSOC);
-  foreach ((array)$values as $key => $value) {
-    $file->{$key} = $value;
+  $result = db_query('SELECT * FROM {upload} u WHERE u.fid IN (' . db_placeholders(array_keys($files)) . ')', array_keys($files))->fetchAll(PDO::FETCH_ASSOC);
+  foreach ($result as $record) {
+    foreach ((array) $record as $key => $value) {
+      $files[$record['fid']]->$key = $value;
+    }
   }
 }
 
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.221
diff -u -p -r1.221 upload.module
--- modules/upload/upload.module	16 Dec 2008 22:05:51 -0000	1.221
+++ modules/upload/upload.module	19 Dec 2008 00:51:09 -0000
@@ -270,11 +270,13 @@ function upload_form_alter(&$form, $form
 /**
  * Implementation of hook_file_load().
  */
-function upload_file_load(&$file) {
+function upload_file_load($files) {
   // Add the upload specific data into the file object.
-  $values = db_query('SELECT * FROM {upload} u WHERE u.fid = :fid', array(':fid' => $file->fid))->fetch(PDO::FETCH_ASSOC);
-  foreach ((array)$values as $key => $value) {
-    $file->{$key} = $value;
+  $result = db_query('SELECT * FROM {upload} u WHERE u.fid IN (' . db_placeholders(array_keys($files)) . ')', array_keys($files))->fetchAll(PDO::FETCH_ASSOC);
+  foreach ($result as $record) {
+    foreach ((array) $record as $key => $value) {
+      $files[$record['fid']]->$key = $value;
+    }
   }
 }
 
@@ -303,9 +305,36 @@ function upload_file_delete(&$file) {
  * Implementation of hook_nodeapi_load().
  */
 function upload_nodeapi_load($nodes, $types) {
+  // Collect all the revision ids for nodes with upload enabled.
+  $node_vids = array();
   foreach ($nodes as $node) {
     if (variable_get("upload_$node->type", 1) == 1) {
-      $node->files = upload_load($node);
+      $node_vids[$node->vid] = $node->vid;
+    }
+  }
+  // Fetch the fids associated with these node revisions.
+  $result = db_query('SELECT u.fid, u.vid FROM {upload} u WHERE u.vid IN (' . db_placeholders($node_vids) . ') ORDER BY u.weight, u.fid', $node_vids);
+
+  // We need to both get the fids from the result, and retain each complete
+  // record for later use.
+  $fids = array();
+  $uploads = array();
+  foreach ($result as $record) {
+    $fids[] = $record->fid;
+    $uploads[] = $record;
+  }
+
+  // Load all file objects required.
+  $files = file_load_multiple($fids);
+
+  foreach ($nodes as $node) {
+    if (isset($node_vids[$node->vid])) {
+      $node->files = array();
+      foreach ($uploads as $upload) {
+        if ($upload->vid == $node->vid) {
+          $node->files[$upload->fid] = $files[$upload->fid];
+        }
+      }
     }
   }
 }
@@ -593,19 +622,6 @@ function theme_upload_form_new($form) {
   return $output;
 }
 
-function upload_load($node) {
-  $files = array();
-
-  if ($node->vid) {
-    $result = db_query('SELECT u.fid FROM {upload} u WHERE u.vid = :vid ORDER BY u.weight, u.fid', array(':vid' => $node->vid));
-    foreach ($result as $file) {
-      $files[$file->fid] = file_load($file->fid);
-    }
-  }
-
-  return $files;
-}
-
 /**
  * Menu-callback for JavaScript-based uploads.
  */
