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 20:04:46 -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 20:04:47 -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 20:04:47 -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 20:04:48 -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); } } } 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 20:04:48 -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 20:04:49 -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); + } + } + } } } @@ -629,4 +653,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 20:04:49 -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.')); + } +}