Index: includes/cache.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/cache.inc,v retrieving revision 1.27 diff -u -p -r1.27 cache.inc --- includes/cache.inc 12 Oct 2008 04:30:05 -0000 1.27 +++ includes/cache.inc 26 Jan 2009 23:43:11 -0000 @@ -10,14 +10,56 @@ * 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_node', '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. + * @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; + } + } + 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 +70,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 +189,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 @@ -197,6 +254,19 @@ 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 clear all records if the number is above + // a certain threshold. + if (count($cid) > variable_get('cache_clear_threshold', 1000)) { + cache_clear_all('*', $table, TRUE); + } + else { + db_delete($table) + ->condition('cid', $cid, 'IN') + ->execute(); + } + } else { db_delete($table) ->condition('cid', $cid) Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.856 diff -u -p -r1.856 common.inc --- includes/common.inc 23 Jan 2009 14:23:27 -0000 1.856 +++ includes/common.inc 26 Jan 2009 23:43:13 -0000 @@ -4054,7 +4054,7 @@ function drupal_flush_all_caches() { node_types_rebuild(); // Don't clear cache_form - in-progress form submissions may break. // Ordered so clearing the page cache will always be the last action. - $core = array('cache', 'cache_block', 'cache_filter', 'cache_registry', 'cache_page'); + $core = array('cache', 'cache_block', 'cache_filter', 'cache_node', 'cache_registry', 'cache_page'); $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); foreach ($cache_tables as $table) { cache_clear_all('*', $table, TRUE); Index: modules/book/book.module =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.module,v retrieving revision 1.485 diff -u -p -r1.485 book.module --- modules/book/book.module 26 Jan 2009 14:08:42 -0000 1.485 +++ modules/book/book.module 26 Jan 2009 23:43:14 -0000 @@ -720,9 +720,14 @@ function book_build_active_trail($book_l } /** - * Implementation of hook_nodeapi_load(). + * Implementation of hook_nodeapi_after_load(). + * + * Add information about book hierarchy to the node object. This does not use + * hook_nodeapi_load() since any change in the book hierarchy can alter this + * information for a large of number nodes, and because the {menu_links} table + * is denormalized, making this query inexpensive. */ -function book_nodeapi_load($nodes, $types) { +function book_nodeapi_after_load($nodes) { $result = db_query("SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid IN (:nids)", array(':nids' => array_keys($nodes)), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $record) { $nodes[$record['nid']]->book = $record; Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.687 diff -u -p -r1.687 comment.module --- modules/comment/comment.module 26 Jan 2009 14:08:42 -0000 1.687 +++ modules/comment/comment.module 26 Jan 2009 23:43:17 -0000 @@ -881,6 +881,7 @@ function comment_save($edit) { _comment_update_node_statistics($edit['nid']); // Clear the cache so an anonymous user can see his comment being added. cache_clear_all(); + cache_clear_all($edit['nid'], 'cache_node'); // Explain the approval queue if necessary, and then // redirect the user to the node he's commenting on. @@ -2008,6 +2009,8 @@ function _comment_update_node_statistics ->condition('nid', $nid) ->execute(); } + // Clear the node cache. + cache_clear_all($nid, 'cache_node'); } /** Index: modules/node/node.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v retrieving revision 1.8 diff -u -p -r1.8 node.api.php --- modules/node/node.api.php 26 Jan 2009 14:08:43 -0000 1.8 +++ modules/node/node.api.php 26 Jan 2009 23:43:17 -0000 @@ -228,7 +228,7 @@ function hook_nodeapi_insert($node) { * parameter. If your module keeps track of the node types it supports, this * allows for an early return if nothing needs to be done. * - * Due to the internal cache in node_load_multiple(), you should not use this + * Due to the persistent cache in node_load_multiple(), you should not use this * hook to modify information returned from the {node} table itself, since * this may affect the way nodes are returned from the cache in subsequent * calls to the function. @@ -250,6 +250,45 @@ function hook_nodeapi_load($nodes, $type } /** + * Act on nodes after they have been loaded from the database. + * + * This hook should be used for altering or adding information to the node + * object which can not be persistently cached. For example, depending on the + * current user or language, or in situations where the data cannot be + * refreshed easily without clearing the cache for large numbers of nodes. + * You should not alter fields returned from the {node} table here since it may + * interfere with the way nodes are retrieved from memory during the course of + * a page request. + * + * @see book_nodeapi_after_load() + */ +function hook_nodeapi_after_load($nodes) { + global $user; + + foreach ($nodes as $node) { + if ($node->type == 'poll') { + // Determine whether or not this user is allowed to vote. + $node->allowvotes = FALSE; + if (user_access('vote on polls') && $node->active) { + if ($user->uid) { + $result = db_query('SELECT chid FROM {poll_vote} WHERE nid = :nid AND uid = :uid', array(':nid' => $node->nid, ':uid' => $user->uid))->fetch(); + } + else { + $result = db_query("SELECT chid FROM {poll_vote} WHERE nid = :nid AND hostname = :hostname", array(':nid' => $node->nid, ':hostname' => ip_address()))->fetch(); + } + if (isset($result->chid)) { + $node->vote = $result->chid; + } + else { + $node->vote = -1; + $node->allowvotes = TRUE; + } + } + } + } +} + +/** * The node is about to be shown on the add/edit form. * * @param $node Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1018 diff -u -p -r1.1018 node.module --- modules/node/node.module 22 Jan 2009 12:46:06 -0000 1.1018 +++ modules/node/node.module 26 Jan 2009 23:43:18 -0000 @@ -152,6 +152,28 @@ function node_cron() { } /** + * Implementation of hook_modules_enabled(). + */ +function node_modules_enabled($modules) { + foreach ($modules as $module) { + if (drupal_function_exists($module . '_load') || drupal_function_exists($module . '_nodeapi_load')) { + cache_clear_all('*', 'cache_node', TRUE); + } + } +} + +/** + * Implementation of hook_modules_disabled(). + */ +function node_modules_disabled($modules) { + foreach ($modules as $module) { + if (drupal_function_exists($module . '_load') || drupal_function_exists($module . '_nodeapi_load')) { + cache_clear_all('*', 'cache_node', TRUE); + } + } +} + +/** * Gather a listing of links to nodes. * * @param $result @@ -574,6 +596,7 @@ function node_type_delete($type) { */ function node_type_update_nodes($old_type, $type) { db_query("UPDATE {node} SET type = '%s' WHERE type = '%s'", $type, $old_type); + cache_clear_all('*', 'cache_node', TRUE); return db_affected_rows(); } @@ -784,20 +807,21 @@ function node_load_multiple($nids = arra } } - // Exclude any nodes loaded from cache if they don't match $conditions. - // This ensures the same behavior whether loading from memory or database. - if ($conditions) { - foreach ($nodes as $node) { - $node_values = (array) $node; - if (array_diff_assoc($conditions, $node_values)) { - unset($nodes[$node->nid]); - } + // Load any available nodes from the persistent cache. + $cached_nodes = array(); + if ($nids && !$vid && !$conditions) { + $cache = cache_get_multiple($nids, 'cache_node'); + foreach ($cache as $item) { + $cached_nodes[$item->cid] = $item->data; } + // Remove any nodes loaded from the $nids to load. + $nids = array_keys(array_diff_key(array_flip($nids), $cached_nodes)); } // Load any remaining nodes from the database. This is the case if there are // any $nids left to load, if loading a revision, or if $conditions was // passed without $nids. + $queried_nodes = array(); if ($nids || $vid || ($conditions && !$passed_nids)) { $query = db_select('node', 'n'); @@ -867,25 +891,57 @@ function node_load_multiple($nids = arra $function = $module . '_nodeapi_load'; $function($queried_nodes, array_keys($typed_nodes)); } - $nodes += $queried_nodes; - // Add nodes to the cache if we're not loading a revision. + + // Only published nodes and active revisions are cached since these are + // not loaded sufficiently often to justify the additional space and + // complexity required to do so. if (!$vid) { - $node_cache += $queried_nodes; + foreach($queried_nodes as $node) { + if ($node->status) { + cache_set($node->nid, $node, 'cache_node'); + } + } } } - // Ensure that the returned array is ordered the same as the original $nids - // array if this was passed in and remove any invalid nids. - if ($passed_nids) { - // Remove any invalid nids from the array. - $passed_nids = array_intersect_key($passed_nids, $nodes); - foreach ($nodes as $node) { - $passed_nids[$node->nid] = $node; + // Pass all nodes loaded from the persistent cache or the database through + // hook_nodeapi_load_after_load() to allow modules to add information which + // cannot be persistently cached. + if (!empty($queried_nodes) || !empty($cached_nodes)) { + $nodes_to_process = $queried_nodes += $cached_nodes; + module_invoke_all('nodeapi_after_load', $nodes_to_process); + $nodes = $nodes_to_process + $nodes; + // Statically cache the node objects at this point to avoid calling the + // hook multiple times in one request. + if (!$vid) { + $node_cache = $nodes_to_process + $node_cache; + } + } + + // Ensure that the final array of node objects contains the same data in + // the same order whether loaded from cache or the node tables. + // $passed_nids is keyed in the same order as the original $nids array. + // Also remove any node IDs which weren't loaded. + $returned_nodes = $passed_nids ? array_intersect_key($passed_nids, $nodes) : array(); + + foreach ($nodes as $node) { + // Only return nodes which match $conditions. + if ($conditions) { + $node_values = (array) $node; + if (array_diff_assoc($conditions, $node_values)) { + unset($returned_nodes[$node->nid]); + } + else { + $returned_nodes[$node->nid] = $node; + } + } + // If no conditions, simply assign the node to the array. + else { + $returned_nodes[$node->nid] = $node; } - $nodes = $passed_nids; } - return $nodes; + return $returned_nodes; } /** @@ -1075,6 +1131,7 @@ function node_save(&$node) { // Clear the page and block caches. cache_clear_all(); + cache_clear_all($node->nid, 'cache_node'); } /** @@ -1123,6 +1180,7 @@ function node_delete($nid) { watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title)); drupal_set_message(t('@type %title has been deleted.', array('@type' => node_get_types('name', $node), '%title' => $node->title))); } + cache_clear_all($node->nid, 'cache_node'); } /** Index: modules/poll/poll.module =================================================================== RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v retrieving revision 1.286 diff -u -p -r1.286 poll.module --- modules/poll/poll.module 22 Jan 2009 12:46:06 -0000 1.286 +++ modules/poll/poll.module 26 Jan 2009 23:43:18 -0000 @@ -433,30 +433,12 @@ function poll_validate($node, $form) { * Implementation of hook_load(). */ function poll_load($nodes) { - global $user; foreach ($nodes as $node) { $poll = db_query("SELECT runtime, active FROM {poll} WHERE nid = :nid", array(':nid' => $node->nid))->fetch(); // Load the appropriate choices into the $poll object. $poll->choice = db_query("SELECT chid, chtext, chvotes, weight FROM {poll_choice} WHERE nid = :nid ORDER BY weight", array(':nid' => $node->nid))->fetchAllAssoc('chid', PDO::FETCH_ASSOC); - // Determine whether or not this user is allowed to vote. - $poll->allowvotes = FALSE; - if (user_access('vote on polls') && $poll->active) { - if ($user->uid) { - $result = db_query('SELECT chid FROM {poll_vote} WHERE nid = :nid AND uid = :uid', array(':nid' => $node->nid, ':uid' => $user->uid))->fetch(); - } - else { - $result = db_query("SELECT chid FROM {poll_vote} WHERE nid = :nid AND hostname = :hostname", array(':nid' => $node->nid, ':hostname' => ip_address()))->fetch(); - } - if ($result) { - $poll->vote = $result->chid; - } - else { - $poll->vote = -1; - $poll->allowvotes = TRUE; - } - } foreach ($poll as $key => $value) { $nodes[$node->nid]->$key = $value; } @@ -464,6 +446,38 @@ function poll_load($nodes) { } /** + * Implementation of hook_nodeapi_after_load(). + * + * Load user specific information into the node object for a single page + * request. + */ +function poll_nodeapi_after_load($nodes) { + global $user; + + foreach ($nodes as $node) { + if ($node->type == 'poll') { + // Determine whether or not this user is allowed to vote. + $node->allowvotes = FALSE; + if (user_access('vote on polls') && $node->active) { + if ($user->uid) { + $result = db_query('SELECT chid FROM {poll_vote} WHERE nid = :nid AND uid = :uid', array(':nid' => $node->nid, ':uid' => $user->uid))->fetch(); + } + else { + $result = db_query("SELECT chid FROM {poll_vote} WHERE nid = :nid AND hostname = :hostname", array(':nid' => $node->nid, ':hostname' => ip_address()))->fetch(); + } + if (isset($result->chid)) { + $node->vote = $result->chid; + } + else { + $node->vote = -1; + $node->allowvotes = TRUE; + } + } + } + } +} + +/** * Implementation of hook_insert(). */ function poll_insert($node) { @@ -639,6 +653,7 @@ function poll_vote($form, &$form_state) db_query("UPDATE {poll_choice} SET chvotes = chvotes + 1 WHERE chid = %d", $choice); cache_clear_all(); + cache_clear_all($node->nid, 'cache_node'); drupal_set_message(t('Your vote was recorded.')); // Return the user to whatever page they voted from. @@ -812,6 +827,7 @@ function poll_cancel($form, &$form_state // Subtract from the votes. db_query("UPDATE {poll_choice} SET chvotes = chvotes - 1 WHERE chid = %d", $node->vote); + cache_clear_all($node->nid, 'cache_node'); } /** Index: modules/simpletest/tests/cache.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/cache.test,v retrieving revision 1.3 diff -u -p -r1.3 cache.test --- modules/simpletest/tests/cache.test 8 Dec 2008 21:41:54 -0000 1.3 +++ modules/simpletest/tests/cache.test 26 Jan 2009 23:43:18 -0000 @@ -162,6 +162,50 @@ 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(). + $items = cache_get_multiple(array('item1', 'item2'), $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(). + $items = cache_get_multiple(array('item1', 'item2'), $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 { function getInfo() { return array( @@ -224,4 +268,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.')); + } +} Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.304 diff -u -p -r1.304 system.install --- modules/system/system.install 25 Jan 2009 16:46:05 -0000 1.304 +++ modules/system/system.install 26 Jan 2009 23:43:20 -0000 @@ -598,6 +598,8 @@ function system_schema() { $schema['cache_page']['description'] = 'Cache table used to store compressed pages for anonymous users, if page caching is enabled.'; $schema['cache_menu'] = $schema['cache']; $schema['cache_menu']['description'] = 'Cache table for the menu system to store router information as well as generated link trees for various menu/page/user combinations.'; + $schema['cache_node'] = $schema['cache']; + $schema['cache_node']['description'] = 'Cache table for the node module to store fully loaded node objects.'; $schema['cache_registry'] = $schema['cache']; $schema['cache_registry']['description'] = 'Cache table for the code registry system to remember what code files need to be loaded on any given page.'; @@ -3216,6 +3218,17 @@ function system_update_7018() { } /** + * Create the cache_node table. + */ +function system_update_7019() { + $ret = array(); + $schema['cache_node'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_node']['description'] = t('Cache table used to store the output of node_load.'); + db_create_table($ret, 'cache_node', $schema['cache_node']); + return $ret; +} + +/** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */ Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.456 diff -u -p -r1.456 taxonomy.module --- modules/taxonomy/taxonomy.module 26 Jan 2009 14:08:43 -0000 1.456 +++ modules/taxonomy/taxonomy.module 26 Jan 2009 23:43:21 -0000 @@ -322,6 +322,8 @@ function taxonomy_term_save($term) { else { $status = drupal_write_record('taxonomy_term_data', $term); module_invoke_all('taxonomy_term_update', $term); + // This change can affect a large number of nodes, so clear the node cache. + taxonomy_clear_node_cache($term->tid); } $or = db_or()->condition('tid1', $term->tid)->condition('tid2', $term->tid); @@ -395,6 +397,9 @@ function taxonomy_term_delete($tid) { $term = taxonomy_term_load($tid); + // Clear the node cache for all nodes associated with this term. + taxonomy_clear_node_cache($term->tid); + db_query('DELETE FROM {taxonomy_term_data} WHERE tid = %d', $tid); db_query('DELETE FROM {taxonomy_term_hierarchy} WHERE tid = %d', $tid); db_query('DELETE FROM {taxonomy_term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid); @@ -1517,3 +1522,14 @@ function taxonomy_hook_info() { ), ); } + +/** + * Clear the node cache for all nodes associated with a term. + * + * @param $tid + * The taxonomy term ID. + */ +function taxonomy_clear_node_cache($tid) { + $nids = db_query('SELECT nid FROM taxonomy_term_node WHERE tid = :tid', array(':tid' => $tid))->fetchCol(); + cache_clear_all($nids, 'cache_node'); +} Index: modules/translation/translation.module =================================================================== RCS file: /cvs/drupal/drupal/modules/translation/translation.module,v retrieving revision 1.38 diff -u -p -r1.38 translation.module --- modules/translation/translation.module 22 Jan 2009 03:11:54 -0000 1.38 +++ modules/translation/translation.module 26 Jan 2009 23:43:21 -0000 @@ -227,6 +227,7 @@ function translation_nodeapi_insert($nod db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $tnid, 0, $node->translation_source->nid); } db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $tnid, 0, $node->nid); + translation_clear_node_cache($tnid); } } } @@ -244,6 +245,7 @@ function translation_nodeapi_update($nod // This is the source node, asking to mark all translations outdated. db_query("UPDATE {node} SET translate = 1 WHERE tnid = %d AND nid <> %d", $node->tnid, $node->nid); } + translation_clear_node_cache($node->tnid); } } } @@ -278,10 +280,28 @@ function translation_remove_from_set($no db_query('UPDATE {node} SET tnid = %d WHERE tnid = %d', $new_tnid, $node->tnid); } } + translation_clear_node_cache($node->tnid); } } /** + * Clear the node_load() cache for all nodes in the translation set. + * + * This ensures we have the proper translation set information in every node. + * + * @param $tnid + * The translation source nid of the translation set, the identifier + * of the node used to derive all translations in the set. + */ +function translation_clear_node_cache($tnid) { + $nids = array(); + foreach (translation_node_get_translations($tnid) as $node) { + $nids[] = $node->nid; + } + cache_clear_all($nids, 'cache_node'); +} + +/** * Get all nodes in a translation set, represented by $tnid. * * @param $tnid