Index: modules/block/block.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.admin.inc,v
retrieving revision 1.72
diff -u -p -r1.72 block.admin.inc
--- modules/block/block.admin.inc	15 Jan 2010 10:59:21 -0000	1.72
+++ modules/block/block.admin.inc	22 Jan 2010 09:09:57 -0000
@@ -17,27 +17,28 @@ function block_admin_demo($theme = NULL)
 /**
  * Menu callback for admin/structure/block.
  *
- * @param $theme
+ * @param $theme
  *   The theme to display the administration page for. If not provided, defaults
  *   to the currently used theme.
  */
 function block_admin_display($theme = NULL) {
   global $theme_key;
 
-  drupal_theme_initialize();
-
+  // If no $theme is passed, default to the current theme.
   if (!isset($theme)) {
-    // If theme is not specifically set, rehash for the current theme.
+    if (!isset($theme_key)) {
+      drupal_theme_initialize();
+    }
     $theme = $theme_key;
   }
 
   // Fetch and sort blocks.
-  $blocks = _block_rehash($theme);
+  $blocks = _block_rehash();
   $compare_theme = &drupal_static('_block_compare:theme');
   $compare_theme = $theme;
-  usort($blocks, '_block_compare');
+  usort($blocks[$theme], '_block_compare');
 
-  return drupal_get_form('block_admin_display_form', $blocks, $theme);
+  return drupal_get_form('block_admin_display_form', $blocks[$theme], $theme);
 }
 
 /**
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.407
diff -u -p -r1.407 block.module
--- modules/block/block.module	13 Jan 2010 05:40:03 -0000	1.407
+++ modules/block/block.module	22 Jan 2010 09:09:57 -0000
@@ -294,93 +294,103 @@ function _block_get_renderable_array($li
 }
 
 /**
- * Update the 'block' DB table with the blocks currently exported by modules.
- *
- * @param $theme
- *   The theme to rehash blocks for. If not provided, defaults to the currently
- *   used theme.
+ * Rebuild the {block} table to reflect module and theme changes.
  *
  * @return
- *   Blocks currently exported by modules.
+ *   Array of blocks exported by modules, keyed by theme name.
  */
-function _block_rehash($theme = NULL) {
-  global $theme_key;
+function _block_rehash() {
+  // Prevent race conditions by starting a transaction.
+  $transaction = db_transaction();
 
-  drupal_theme_initialize();
-
-  if (!isset($theme)) {
-    // If theme is not specifically set, rehash for the current theme.
-    $theme = $theme_key;
+  // Build array of themes whose blocks need to be rebuilt.
+  $themes = array();
+  foreach(list_themes() as $theme) {
+    if ($theme->status || $theme->name == variable_get('admin_theme', '0')) {
+      $themes[] = $theme->name;
+    }
   }
 
+  // Fetch the existing {block} table, keying it by a hash that we can easily
+  // look up.
+  $result = db_query('SELECT * FROM {block}', array(), array(
+    'fetch' => PDO::FETCH_ASSOC,
+  ));
   $old_blocks = array();
-  $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $theme));
   foreach ($result as $old_block) {
-    $old_block = is_object($old_block) ? get_object_vars($old_block) : $old_block;
-    $old_blocks[$old_block['module']][$old_block['delta']] = $old_block;
+    $hash_key = $old_block['theme'] . ':' . $old_block['module'] . ':' . $old_block['delta'];
+    $old_blocks[$hash_key] = $old_block;
   }
 
   $blocks = array();
-  // Valid region names for the theme.
-  $regions = system_region_list($theme);
-
   foreach (module_implements('block_info') as $module) {
-    $module_blocks = module_invoke($module, 'block_info');
-    if ($module_blocks) {
-      foreach ($module_blocks as $delta => $block) {
-        if (empty($old_blocks[$module][$delta])) {
-          // If it's a new block, add identifiers.
-          $block['module'] = $module;
-          $block['delta']  = $delta;
-          $block['theme']  = $theme;
-          if (!isset($block['pages'])) {
-            // {block}.pages is type 'text', so it cannot have a
-            // default value, and not null, so we need to provide
-            // value if the module did not.
-            $block['pages']  = '';
-          }
-          // Add defaults and save it into the database.
-          drupal_write_record('block', $block);
-          // Set region to none if not enabled.
-          $block['region'] = $block['status'] ? $block['region'] : BLOCK_REGION_NONE;
-          // Add to the list of blocks we return.
-          $blocks[] = $block;
-        }
-        else {
-          // If it's an existing block, database settings should overwrite
-          // the code. But aside from 'info' everything that's definable in
-          // code is stored in the database and we do not store 'info', so we
-          // do not need to update the database here.
-          // Add 'info' to this block.
-          $old_blocks[$module][$delta]['info'] = $block['info'];
-          // If the region name does not exist, disable the block and assign it to none.
-          if (!empty($old_blocks[$module][$delta]['region']) && !isset($regions[$old_blocks[$module][$delta]['region']])) {
-            drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $old_blocks[$module][$delta]['info'], '%region' => $old_blocks[$module][$delta]['region'])), 'warning');
-            $old_blocks[$module][$delta]['status'] = 0;
-            $old_blocks[$module][$delta]['region'] = BLOCK_REGION_NONE;
+    if ($module_blocks = module_invoke($module, 'block_info')) {
+      foreach ($themes as $theme) {
+        // Get the regions this theme supports.
+        $regions = system_region_list($theme);
+        foreach ($module_blocks as $delta => $block) {
+          $hash_key = $theme . ':' . $module . ':' . $delta;
+          if (isset($old_blocks[$hash_key])) {
+            $cache_mode = isset($block['cache']) ? $block['cache'] : DRUPAL_CACHE_PER_ROLE;
+            // Merge the database's values with the hard-coded values. The
+            // cache mode should always reflect the module's definition.
+            $block = array('cache' => $cache_mode) + $old_blocks[$hash_key] + $block;
+            // If the block is enabled and the assigned region is not supported
+            // by this theme, disable the block and unassign the region.
+            if ($block['status'] && $block['region'] != BLOCK_REGION_NONE && !isset($regions[$block['region']])) {
+              drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block['info'], '%region' => $block['region'])), 'warning');
+              $block['region'] = '';
+              $block['status'] = 0;
+            }
+						// Unset the block in $old_blocks. Remaining blocks in that array
+            // will be cleaned up later.
+            unset($old_blocks[$hash_key]);
+            // Tell drupal_write_record() to update the existing row.
+            $update = array('bid');
           }
           else {
-            $old_blocks[$module][$delta]['region'] = $old_blocks[$module][$delta]['status'] ? $old_blocks[$module][$delta]['region'] : BLOCK_REGION_NONE;
+            $defaults = array(
+              'module' => $module,
+              'delta' => $delta,
+              'theme' => $theme,
+              'status' => 0,
+              'weight' => 0,
+              'region' => '',
+              'custom' => 0,
+              'visibility' => 0,
+              'pages' => '',
+              'title' => '',
+              'cache' => DRUPAL_CACHE_PER_ROLE,
+            );
+            $block += $defaults;
+            // Tell drupal_write_record() to insert a new record.
+            $update = array();
           }
-          // Add this block to the list of blocks we return.
-          $blocks[] = $old_blocks[$module][$delta];
-          // Remove this block from the list of blocks to be deleted.
-          unset($old_blocks[$module][$delta]);
+
+          // Save the block to the {block} table.
+          drupal_write_record('block', $block, $update);
+
+          // An empty region is '' in the database, and BLOCK_REGION_NONE in
+          // the code.
+          // TODO: standardize this.
+          if ($block['region'] == '') {
+            $block['region'] = BLOCK_REGION_NONE;
+          }
+
+          // Add to the list of blocks we return. 
+          $blocks[$theme][] = $block;
         }
       }
     }
   }
 
-  // Remove blocks that are no longer defined by the code from the database.
-  foreach ($old_blocks as $module => $old_module_blocks) {
-    foreach ($old_module_blocks as $delta => $block) {
-      db_delete('block')
-        ->condition('module', $module)
-        ->condition('delta', $delta)
-        ->condition('theme', $theme)
-        ->execute();
-    }
+  // Clean up the database, removing blocks that are no longer defined.
+  foreach ($old_blocks as $old_block) {
+    db_delete('block')
+      ->condition('bid', $old_block['bid'])
+      ->execute();
   }
+
   return $blocks;
 }
 
@@ -835,6 +845,8 @@ function _block_get_cache_id($block) {
  * Implements hook_flush_caches().
  */
 function block_flush_caches() {
+  // Rebuild the {block} table.
+  _block_rehash();
   return array('cache_block');
 }
 
Index: modules/block/block.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.test,v
retrieving revision 1.34
diff -u -p -r1.34 block.test
--- modules/block/block.test	9 Jan 2010 23:03:21 -0000	1.34
+++ modules/block/block.test	22 Jan 2010 09:09:57 -0000
@@ -46,9 +46,9 @@ class BlockTestCase extends DrupalWebTes
 
     // Confirm that the custom block has been created, and then query the created bid.
     $this->assertText(t('The block has been created.'), t('Custom block successfully created.'));
-    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+    $bid = db_query('SELECT bid FROM {block_custom} WHERE info = :info', array(':info' => $custom_block['info']))->fetchField();
 
-    // Check to see if the custom block was created by checking that it's in the database..
+    // Check to see if the custom block was created by checking that it's in the database.
     $this->assertNotNull($bid, t('Custom block found in database'));
 
     // Check if the block can be moved to all availble regions.
@@ -170,7 +170,7 @@ class BlockTestCase extends DrupalWebTes
     // Check to see if the block was created by checking that it's in the database.
     $this->assertNotNull($bid, t('Block found in database'));
 
-    // Check if the block can be moved to all availble regions.
+    // Check if the block can be moved to all available regions.
     foreach ($this->regions as $region) {
       $this->moveBlockToRegion($block, $region);
     }
@@ -220,6 +220,27 @@ class BlockTestCase extends DrupalWebTes
     $xpath = '//div[@class="' . $region['class'] . '"]//div[@id="block-' . $block['module'] . '-' . $block['delta'] . '"]/*';
     $this->assertFieldByXPath($xpath, FALSE, t('Custom block found in %region_name region.', array('%region_name' => $region['name'])));
   }
+
+  /**
+   * Test _block_rehash().
+   */
+  function testBlockRehash() {
+    module_enable(array('block_test'));
+    $this->assertTrue(module_exists('block_test'), t('Test block module enabled.'));
+    $blocks = _block_rehash();
+
+    // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE.
+    $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_block'")->fetchField();
+    $this->assertEqual($current_caching, DRUPAL_CACHE_PER_ROLE, t('Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.'));    
+
+    // Disable block caching.
+    variable_set('block_test_caching', DRUPAL_NO_CACHE);
+    _block_rehash();
+
+    // Verify that changing the caching mode affected the database.
+    $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_block'")->fetchField();
+    $this->assertEqual($current_caching, DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE."));
+  }
 }
 
 class NonDefaultBlockAdmin extends DrupalWebTestCase {
