Index: install.php
===================================================================
RCS file: /cvs/drupal/drupal/install.php,v
retrieving revision 1.178
diff -u -p -r1.178 install.php
--- install.php	8 Jun 2009 04:33:35 -0000	1.178
+++ install.php	17 Jun 2009 21:16:40 -0000
@@ -849,6 +849,7 @@ function _install_module_batch($module, 
   // modules possibly depending on it can safely perform their installation
   // steps.
   module_enable(array($module));
+  module_invoke_all('modules_installed', array($module));
   $context['results'][] = $module;
   $context['message'] = st('Installed %module module.', array('%module' => $module_name));
 }
Index: includes/cache.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache.inc,v
retrieving revision 1.34
diff -u -p -r1.34 cache.inc
--- includes/cache.inc	16 Jun 2009 23:48:09 -0000	1.34
+++ includes/cache.inc	17 Jun 2009 21:16:42 -0000
@@ -46,6 +46,21 @@ function cache_get($cid, $bin = '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 $bin
+ *   The cache bin where the data is stored.
+ * @return
+ *   An array of the items successfully returned from cache indexed by cid.
+ */
+function cache_get_multiple(array &$cids, $bin = 'cache') {
+  return _cache_get_object($bin)->getMultiple($cids);
+}
+
+/**
  * Store data in the persistent cache.
  *
  * The persistent cache is split up into several cache bins. In the default
@@ -198,6 +213,18 @@ interface DrupalCacheInterface {
   function get($cid);
 
   /**
+   * 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.
+   * @return
+   *   An array of the items successfully returned from cache indexed by cid.
+   */
+   function getMultiple(&$cids);
+
+  /**
    * Store data in the persistent cache.
    *
    * @param $cid
@@ -249,9 +276,41 @@ class DrupalDatabaseCache implements Dru
   }
 
   function get($cid) {
+    // Garbage collection necessary when enforcing a minimum cache lifetime.
+    $this->garbageCollection($this->bin);
+    $cache = db_query("SELECT data, created, headers, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
+
+    return $this->prepareItem($cache);
+  }
+
+  function getMultiple(&$cids) {
+    // Garbage collection necessary when enforcing a minimum cache lifetime.
+    $this->garbageCollection($this->bin);
+    $query = db_select($this->bin);
+    $query->fields($this->bin, array('cid', 'data', 'created', 'headers', 'expire', 'serialized'));
+    $query->condition($this->bin . '.cid', $cids, 'IN');
+    $result = $query->execute();
+    $cache = array();
+    foreach ($result as $item) {
+      $item = $this->prepareItem($item);
+      if ($item) {
+        $cache[$item->cid] = $item;
+      }
+    }
+    $cids = array_keys(array_diff_key(array_flip($cids), $cache));
+    return $cache;
+  }
+
+  /**
+   * Garbage collection for get() and getMultiple().
+   *
+   * @param $bin
+   *   The bin being requested.
+   */
+  protected function garbageCollection() {
     global $user;
 
-    // Garbage collection is necessary when enforcing a minimum cache lifetime.
+    // Garbage collection necessary when enforcing a minimum cache lifetime
     $cache_flush = variable_get('cache_flush_' . $this->bin, 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.
@@ -262,13 +321,31 @@ class DrupalDatabaseCache implements Dru
         ->condition('expire', $cache_flush, '<=')
         ->execute();
     }
+  }
 
-    $cache = db_query("SELECT data, created, headers, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
-
+  /**
+   * Prepare a cached item.
+   *
+   * Checks that items are either permanent or did not expire, 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.
+   */
+  protected function prepareItem($cache) {
     if (!isset($cache->data)) {
       return FALSE;
     }
-
+    // If the data is permanent or we are not enforcing a minimum cache lifetime
+    // always return the cached data.
+    if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) {
+      if ($cache->serialized) {
+        $cache->data = unserialize($cache->data);
+      }
+    }
     // If enforcing a minimum cache lifetime, validate that the data is
     // currently valid for this user before we return it by making sure the cache
     // entry was created before the timestamp in the current session's cache
@@ -280,9 +357,6 @@ class DrupalDatabaseCache implements Dru
       return FALSE;
     }
 
-    if ($cache->serialized) {
-      $cache->data = unserialize($cache->data);
-    }
     if (isset($cache->headers)) {
       $cache->headers = unserialize($cache->headers);
     }
@@ -357,6 +431,15 @@ class DrupalDatabaseCache implements Dru
             ->execute();
         }
       }
+      elseif (is_array($cid)) {
+        // Delete in chunks when a large array is passed.
+        do {
+          db_delete($this->bin)
+            ->condition('cid', array_splice($cid, 0, 1000), 'IN')
+            ->execute();
+        }
+        while (count($cid));
+      }
       else {
         db_delete($this->bin)
           ->condition('cid', $cid)
Index: includes/install.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/install.inc,v
retrieving revision 1.93
diff -u -p -r1.93 install.inc
--- includes/install.inc	8 Jun 2009 04:33:35 -0000	1.93
+++ includes/install.inc	17 Jun 2009 21:16:42 -0000
@@ -610,6 +610,9 @@ function drupal_uninstall_modules($modul
     drupal_load('module', $module);
     $paths = module_invoke($module, 'menu');
 
+    // Invoke hook_modules_uninstalled() to let other modules act.
+    module_invoke_all('modules_uninstalled', array($module));
+
     // Uninstall the module.
     module_load_install($module);
     module_invoke($module, 'uninstall');
@@ -645,11 +648,6 @@ function drupal_uninstall_modules($modul
 
     drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
   }
-
-  if (!empty($module_list)) {
-    // Call hook_module_uninstall to let other modules act
-    module_invoke_all('modules_uninstalled', $module_list);
-  }
 }
 
 /**
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.22
diff -u -p -r1.22 field.attach.inc
--- modules/field/field.attach.inc	7 Jun 2009 00:00:57 -0000	1.22
+++ modules/field/field.attach.inc	17 Jun 2009 21:16:43 -0000
@@ -400,28 +400,32 @@ function field_attach_load($obj_type, $o
   $info = field_info_fieldable_types($obj_type);
   $cacheable = $load_current && $info['cacheable'];
 
-  $queried_objects = array();
+  if (empty($objects)) {
+    return;
+  }
+
+  // Assume all objects will need to be queried. Objects found in the cache
+  // will be removed from the list.
+  $queried_objects = $objects;
 
-  // Fetch avaliable objects from cache.
+  // Fetch available objects from cache, if applicable.
   if ($cacheable) {
+    $bin = 'cache_field_' . $obj_type;
+    $cids = array_keys($objects);
+    $cache = cache_get_multiple($cids, $bin);
+    // Put the cached field values back into the objects and remove them from
+    // the list of objects to query.
     foreach ($objects as $id => $object) {
-      $cid = "field:$obj_type:$id";
-      if ($cached = cache_get($cid, 'cache_field')) {
-        foreach ($cached->data as $key => $value) {
-          $object->$key = $value;
+      if (isset($cache[$id])) {
+        unset($queried_objects[$id]);
+        foreach ($cache[$id]->data as $field_name => $values) {
+          $object->$field_name = $values;
         }
       }
-      else {
-        $queried_objects[$id] = $objects[$id];
-      }
     }
   }
-  else {
-    $queried_objects = $objects;
-  }
-
 
-  // Fetch other objects from the database.
+  // Fetch other objects from their storage location.
   if ($queried_objects) {
     // The invoke order is:
     // - hook_field_attach_pre_load()
@@ -460,8 +464,7 @@ function field_attach_load($obj_type, $o
         foreach ($instances as $instance) {
           $data[$instance['field_name']] = $queried_objects[$id]->{$instance['field_name']};
         }
-        $cid = "field:$obj_type:$id";
-        cache_set($cid, $data, 'cache_field');
+        cache_set($id, $data, $bin);
       }
     }
   }
@@ -648,7 +651,7 @@ function field_attach_insert($obj_type, 
 
   list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
   if ($cacheable) {
-    cache_clear_all("field:$obj_type:$id", 'cache_field');
+    cache_clear_all("$id", 'cache_field_' . $obj_type);
   }
 }
 
@@ -676,7 +679,7 @@ function field_attach_update($obj_type, 
 
   list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
   if ($cacheable) {
-    cache_clear_all("field:$obj_type:$id", 'cache_field');
+    cache_clear_all("$id", 'cache_field_' . $obj_type);
   }
 }
 
@@ -701,7 +704,7 @@ function field_attach_delete($obj_type, 
 
   list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
   if ($cacheable) {
-    cache_clear_all("field:$obj_type:$id", 'cache_field');
+    cache_clear_all("$id", 'cache_field_' . $obj_type);
   }
 }
 
Index: modules/field/field.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.install,v
retrieving revision 1.9
diff -u -p -r1.9 field.install
--- modules/field/field.install	28 May 2009 10:05:32 -0000	1.9
+++ modules/field/field.install	17 Jun 2009 21:16:43 -0000
@@ -141,7 +141,17 @@ function field_schema() {
       'widget_type' => array('widget_type'),
     ),
   );
-  $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache');
+  $cache_schema = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_field'] = $cache_schema;
+
+  // Add a cache table for each fieldable entity.
+  $fieldable = module_invoke_all('fieldable_info');
+  foreach ($fieldable as $entity => $info) {
+    $schema['cache_field_' . $entity] = $cache_schema;
+  }
+  // Ensure user module always has an entry in the schema since user_load()
+  // may be called during the installation of user module itself.
+  $schema['cache_field_user'] = $cache_schema;
 
   return $schema;
 }
Index: modules/field/field.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.module,v
retrieving revision 1.13
diff -u -p -r1.13 field.module
--- modules/field/field.module	6 Jun 2009 16:17:30 -0000	1.13
+++ modules/field/field.module	17 Jun 2009 21:16:43 -0000
@@ -187,6 +187,20 @@ function field_theme() {
  */
 function field_modules_installed($modules) {
   field_cache_clear();
+
+  // Create {cache_field_$type} tables for each new entity type when modules are
+  // disabled.
+  $schema = drupal_get_schema_unprocessed('system', 'cache');
+  $ret = array();
+  foreach ($modules as $module) {
+    if ($fieldable = module_invoke($module, 'fieldable_info')) {
+      foreach ($fieldable as $entity => $info) {
+        if (!db_table_exists('cache_field_' . $entity)) {
+          db_create_table($ret, 'cache_field_' . $entity, $schema);
+        }
+      }
+    }
+  }
 }
 
 /**
@@ -197,6 +211,16 @@ function field_modules_uninstalled($modu
   foreach ($modules as $module) {
     // TODO D7: field_module_delete is not yet implemented
     // field_module_delete($module);
+
+    // Remove {cache_field_$type} tables when modules are disabled.
+    $ret = array();
+    if ($fieldable = module_invoke($module, 'fieldable_info')) {
+      foreach ($fieldable as $entity => $info) {
+        if (db_table_exists('cache_field_' . $entity)) {
+          db_drop_table($ret, 'cache_field_' . $entity);
+        }
+      }
+    }
   }
 }
 
@@ -346,6 +370,10 @@ function field_build_modes($obj_type) {
  */
 function field_cache_clear($rebuild_schema = FALSE) {
   cache_clear_all('*', 'cache_field', TRUE);
+  $fieldable = field_info_fieldable_types();
+  foreach ($fieldable as $type => $info) {
+    cache_clear_all('*', 'cache_field_' . $type, TRUE);
+  }
 
   module_load_include('inc', 'field', 'field.info');
   _field_info_collate_types(TRUE);
@@ -629,4 +657,4 @@ function template_preprocess_field(&$var
 
 /**
  * @} End of "defgroup field"
- */
\ No newline at end of file
+ */
Index: modules/simpletest/tests/cache.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/cache.test,v
retrieving revision 1.7
diff -u -p -r1.7 cache.test
--- modules/simpletest/tests/cache.test	16 Jun 2009 23:48:09 -0000	1.7
+++ modules/simpletest/tests/cache.test	17 Jun 2009 21:16:44 -0000
@@ -162,6 +162,56 @@ class CacheSavingCase extends CacheTestC
   }
 }
 
+/**
+ * Test cache_get_multiple().
+ */
+class CacheGetMultipleUnitTest extends CacheTestCase {
+
+  public static 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_bin = 'cache_page';
+    parent::setUp();
+  }
+
+  /**
+   * Test cache_get_multiple().
+   */
+  function testCacheMultiple() {
+    $item1 = $this->randomName(10);
+    $item2 = $this->randomName(10);
+    cache_set('item1', $item1, $this->default_bin);
+    cache_set('item2', $item2, $this->default_bin);
+    $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_bin);
+    $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_bin);
+
+    // Confirm that only one item is returned by cache_get_multiple().
+    $item_ids = array('item1', 'item2');
+    $items = cache_get_multiple($item_ids, $this->default_bin);
+    $this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.'));
+    $this->assertFalse(isset($items['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 +274,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_bin);
+    cache_set('test_cid_clear2', $this->default_value, $this->default_bin);
+    cache_set('test_cid_clear3', $this->default_value, $this->default_bin);
+    $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_bin);
+    $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 bin is cleared
+    // when the threshold is exceeded.
+    variable_set('cache_clear_threshold', 2);
+    cache_set('test_cid_clear1', $this->default_value, $this->default_bin);
+    cache_set('test_cid_clear2', $this->default_value, $this->default_bin);
+    $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_bin);
+    $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.'));
+  }
+}
