Index: includes/cache.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache.inc,v
retrieving revision 1.28
diff -u -p -r1.28 cache.inc
--- includes/cache.inc	3 Feb 2009 12:30:14 -0000	1.28
+++ includes/cache.inc	19 Apr 2009 11:58:33 -0000
@@ -10,14 +10,58 @@
  *   The cache ID of the data to retrieve.
  * @param $table
  *   The table $table to store the data in. Valid core values are
- *   'cache_filter', 'cache_menu', 'cache_page', or 'cache' for
- *   the default cache.
+ *   'cache_block', 'cache_filter', 'cache_menu', 'cache_page',
+ *   'cache_registry', or 'cache' for the default cache.
  * @return The cache or FALSE on failure.
  */
 function cache_get($cid, $table = 'cache') {
+  // Garbage collection necessary when enforcing a minimum cache lifetime.
+  _cache_garbage_collection($table);
+  $cache = db_query("SELECT data, created, headers, expire, serialized FROM {" . $table . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
+
+  return _cache_prepare_item($cache);
+}
+
+/**
+ * Return data from the persistent cache when given an array of cache IDs.
+ *
+ * @param $cids
+ *   An array of cache IDs for the data to retrieve. This is passed by
+ *   reference, and will have the IDs successfully returned from cache removed.
+ * @param $table
+ *   The table $table to store the data in. Valid core values are
+ *   'cache_block', 'cache_filter', 'cache_menu', 'cache_node', 'cache_page',
+ *   'cache_registry', or 'cache' for the default cache.
+ * @return
+ *   An array of the items successfully returned from cache indexed by cid.
+ */
+function cache_get_multiple(array &$cids, $table = 'cache') {
+  // Garbage collection necessary when enforcing a minimum cache lifetime.
+  _cache_garbage_collection($table);
+  $query = db_select($table);
+  $query->fields($table, array('cid', 'data', 'created', 'headers', 'expire', 'serialized'));
+  $query->condition($table . '.cid', $cids, 'IN');
+  $result = $query->execute();
+  $cache = array();
+  foreach ($result as $item) {
+    $item = _cache_prepare_item($item);
+    if ($item) {
+      $cache[$item->cid] = $item->data;
+    }
+  }
+  $cids = array_keys(array_diff_key(array_flip($cids), $cache));
+  return $cache;
+}
+
+/**
+ * Garbage collection for cache_get() and cache_get_multiple().
+ *
+ * @param $table
+ *  The table being requested.
+ */
+function _cache_garbage_collection($table) {
   global $user;
 
-  // Garbage collection necessary when enforcing a minimum cache lifetime
   $cache_flush = variable_get('cache_flush', 0);
   if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) {
     // Reset the variable immediately to prevent a meltdown in heavy load situations.
@@ -28,8 +72,23 @@ function cache_get($cid, $table = 'cache
       ->condition('expire', $cache_flush, '<=')
       ->execute();
   }
+}
 
-  $cache = db_query("SELECT data, created, headers, expire, serialized FROM {" . $table . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
+
+/**
+ * Utility function for preparing a cached item.
+ *
+ * Checks that items are permanent or have not expired and unserializes
+ * data as appropriate.
+ *
+ * @param $cache
+ *   An item loaded from cache_get() or cache_get_multiple().
+ *
+ * @return
+ *   The item with data unserialized as appropriate or FALSE if there is no
+ *   valid item to load.
+ */
+function _cache_prepare_item($cache) {
   if (isset($cache->data)) {
     // If the data is permanent or we're not enforcing a minimum cache lifetime
     // always return the cached data.
@@ -132,8 +191,8 @@ function cache_set($cid, $data, $table =
  * entries will be cleared from the cache_page and cache_block tables.
  *
  * @param $cid
- *   If set, the cache ID to delete. Otherwise, all cache entries that can
- *   expire are deleted.
+ *   If set, the cache ID to delete, or an array of cache IDs to delete.
+ *   Otherwise, all cache entries that can expire are deleted.
  *
  * @param $table
  *   If set, the table $table to delete from. Mandatory
@@ -199,6 +258,17 @@ function cache_clear_all($cid = NULL, $t
           ->execute();
       }
     }
+    elseif (is_array($cid)) {
+      // This has the potential to cause max_allowed_packet errors with large
+      // large numbers of records, so chunk the deletes.
+      $cache_clear_threshold = variable_get('cache_clear_threshold', 1000);
+      do {
+        db_delete($table)
+          ->condition('cid', array_splice($cid, 0, $cache_clear_threshold), 'IN')
+          ->execute();
+      }
+      while (count($cid));
+    }
     else {
       db_delete($table)
         ->condition('cid', $cid)
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.10
diff -u -p -r1.10 field.attach.inc
--- modules/field/field.attach.inc	13 Apr 2009 05:18:17 -0000	1.10
+++ modules/field/field.attach.inc	19 Apr 2009 11:58:39 -0000
@@ -257,13 +257,19 @@ function _field_attach_form($obj_type, $
  */
 function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
   $queried_objects = array();
+  $cids = array();
 
   // Fetch avaliable objects from cache.
   foreach ($objects as $object) {
     list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
-    $cid = "field:$obj_type:$id:$vid";
-    if ($cacheable && $cached = cache_get($cid, 'cache_field')) {
-      foreach ($cached->data as $key => $value) {
+    $cids[] = "field:$obj_type:$id:$vid";
+  }
+  $cache = cache_get_multiple($cids, 'cache_field');
+
+  foreach ($objects as $object) {
+    list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+    if ($cacheable && isset($cache["field:$obj_type:$id:$vid"])) {
+      foreach ($cache["field:$obj_type:$id:$vid"] as $key => $value) {
         $object->$key = $value;
       }
     }
Index: modules/simpletest/tests/cache.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/cache.test,v
retrieving revision 1.4
diff -u -p -r1.4 cache.test
--- modules/simpletest/tests/cache.test	31 Mar 2009 01:49:53 -0000	1.4
+++ modules/simpletest/tests/cache.test	19 Apr 2009 11:58:47 -0000
@@ -162,6 +162,52 @@ class CacheSavingCase extends CacheTestC
   }
 }
 
+/**
+ * Test cache_get_multiple().
+ */
+class CacheGetMultipleUnitTest extends CacheTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('Fetching multiple cache items'),
+      'description' => t('Confirm that multiple records are fetched correctly.'),
+      'group' => t('Cache'),
+    );
+  }
+
+  function setUp() {
+    $this->default_table = 'cache_page';
+    parent::setUp();
+  }
+  function testCacheMultiple() {
+    $item1 = $this->randomName(10);
+    $item2 = $this->randomName(10);
+    cache_set('item1', $item1, $this->default_table);
+    cache_set('item2', $item2, $this->default_table);
+    $this->assertTrue($this->checkCacheExists('item1', $item1), t('Item 1 is cached.'));
+    $this->assertTrue($this->checkCacheExists('item2', $item2), t('Item 2 is cached.'));
+
+    // Fetch both records from the database with cache_get_multiple().
+    $item_ids = array('item1', 'item2');
+    $items = cache_get_multiple($item_ids, $this->default_table);
+    $this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.'));
+    $this->assertEqual($items['item2']->data, $item2, t('Item was returned from cache successfully.'));
+
+    // Remove one item from the cache.
+    cache_clear_all('item2', $this->default_table);
+
+    // Confirm that only one item is returned by cache_get_multiple().
+    $item_ids = array('item1', 'item2');
+    $items = cache_get_multiple($item_ids, $this->default_table);
+    $this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.'));
+    $this->assertFalse(isset($items['item2']), $item2, t('Item was not returned from the cache.'));
+    $this->assertTrue(count($items) == 1, t('Only valid cache entries returned.'));
+  }
+}
+
+/**
+ * Test cache clearing methods.
+ */
 class CacheClearCase extends CacheTestCase {
   public static function getInfo() {
     return array(
@@ -224,4 +270,41 @@ class CacheClearCase extends CacheTestCa
                       || $this->checkCacheExists('test_cid_clear2', $this->default_value),
                       t('Two caches removed after clearing cid substring with wildcard true.'));
   }
-}
\ No newline at end of file
+
+  /**
+   * Test clearing using an array.
+   */
+  function testClearArray() {
+    // Create three cache entries.
+    cache_set('test_cid_clear1', $this->default_value, $this->default_table);
+    cache_set('test_cid_clear2', $this->default_value, $this->default_table);
+    cache_set('test_cid_clear3', $this->default_value, $this->default_table);
+    $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                      && $this->checkCacheExists('test_cid_clear2', $this->default_value)
+                      && $this->checkCacheExists('test_cid_clear3', $this->default_value),
+                      t('Three cache entries were created.'));
+
+    // Clear two entries using an array.
+    cache_clear_all(array('test_cid_clear1', 'test_cid_clear2'), $this->default_table);
+    $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                       || $this->checkCacheExists('test_cid_clear2', $this->default_value),
+                       t('Two cache entries removed after clearing with an array.'));
+
+    $this->assertTrue($this->checkCacheExists('test_cid_clear3', $this->default_value),
+                      t('Entry was not cleared from the cache'));
+
+    // Set the cache clear threshold to 2 to confirm that the full table is cleared
+    // when the threshold is exceeded.
+    variable_set('cache_clear_threshold', 2);
+    cache_set('test_cid_clear1', $this->default_value, $this->default_table);
+    cache_set('test_cid_clear2', $this->default_value, $this->default_table);
+    $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                      && $this->checkCacheExists('test_cid_clear2', $this->default_value),
+                      t('Two cache entries were created.'));
+    cache_clear_all(array('test_cid_clear1', 'test_cid_clear2', 'test_cid_clear3'), $this->default_table);
+    $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                       || $this->checkCacheExists('test_cid_clear2', $this->default_value)
+                       || $this->checkCacheExists('test_cid_clear3', $this->default_value),
+                       t('All cache entries removed when the array exceeded the cache clear threshold.'));
+  }
+}
