Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.255.2.37
diff -u -p -r1.255.2.37 menu.inc
--- includes/menu.inc	4 Nov 2010 09:58:34 -0000	1.255.2.37
+++ includes/menu.inc	20 Nov 2010 08:35:27 -0000
@@ -1737,6 +1737,127 @@ function menu_router_build($reset = FALS
     // Alter the menu as defined in modules, keys are like user/%user.
     drupal_alter('menu', $callbacks);
     $menu = _menu_router_build($callbacks);
+
+    // Calculate the new content for the menu router table.
+    $rows_new = array();
+    foreach ($menu as $path => $v) {
+      $item = $menu[$path];
+      $rows_new[$path] = array(
+        'path' => $path,
+        'load_functions' => $item['load_functions'],
+        'to_arg_functions' => $item['to_arg_functions'],
+        'access_callback' => $item['access callback'],
+        'access_arguments' => serialize($item['access arguments']),
+        'page_callback' => $item['page callback'],
+        'page_arguments' => serialize($item['page arguments']),
+        'fit' => $item['_fit'],
+        'number_parts' => $item['_number_parts'],
+        'tab_parent' => $item['tab_parent'],
+        'tab_root' => $item['tab_root'],
+        'title' => $item['title'],
+        'title_callback' => $item['title callback'],
+        'title_arguments' => $item['title arguments'] ? serialize($item['title arguments']) : '',
+        'type' => $item['type'],
+        'block_callback' => $item['block callback'],
+        'description' => $item['description'],
+        'position' => $item['position'],
+        'weight' => $item['weight'],
+        'file' => $item['include file'],
+      );
+    }
+
+    // Get the old rows from the cache if we can, otherwise assume an initial build
+    // and empty menu_router.
+    $rows_old = array();
+    if ($data = cache_get('menu_router_build_old_rows')) {
+      $rows_old = $data->data;
+
+      // Empty the cache immediatly in case there is an error whilst processing changes
+      // so we don't get left in a indeterminant state.
+      cache_clear_all('menu_router_build_old_rows', 'cache');
+    }
+    else {
+      db_query('TRUNCATE TABLE {menu_router}');
+    }
+
+    // Cacluate the changes.
+    $changes_insert = array();
+    $changes_update = array();
+    $changes_delete = array();
+    foreach ($rows_new as $path => $row_new) {
+      // If only a new path, then do an insert.
+      if (!isset($rows_old[$path])) {
+        $changes_insert[$path] = $row_new;
+      }
+      else {
+        // Get a sepcific subset of changes for this row.
+        $row_old = $rows_old[$path];
+        $row_changes = array();
+        foreach ($row_new as $key => $value) {
+          if ($value != $row_old[$key]) {
+            $row_changes[$key] = $value;
+          }
+        }
+
+        // If nothing changed, do nothing.
+        if (!empty($row_changes)) {
+          $changes_update[$path] = $row_changes;
+        }
+
+        // Make sure this row is not triggered for deletion.
+        unset($rows_old[$path]);
+      }
+    }
+
+    // Delete remaining rows.
+    foreach ($rows_old as $path => $row_old) {
+      $changes_delete[$path] = $path;
+    }
+
+    // Perform the inserts.
+    if (!empty($changes_insert)) {
+      // The SQL is always the same, only the values change.
+      $fields = array_keys(current($changes_insert));
+      $tokens = db_placeholders($fields, 'varchar');
+      $fields = implode(', ', $fields);
+      // @todo: ON DUPLICATE KEY UPDATE ?
+      $sql = "INSERT INTO {menu_router} ($fields) VALUES ($tokens)";
+
+      foreach ($changes_insert as $row) {
+        db_query($sql, array_values($row));
+      }
+    }
+
+    // Perform the updates.
+    foreach ($changes_update as $path => $row_changes) {
+      // The path should not change. (It's our key).
+      unset($row_changes['path']);
+
+      $set = $args = array();
+      foreach ($row_changes as $k => $v) {
+        $set[] = "$k = '%s'";
+        $args[] = $v;
+      }
+
+      $set = implode(', ', $set);
+      $sql = "UPDATE {menu_router} SET $set WHERE path = '%s'";
+
+      $args[] = $path;
+      db_query($sql, $args);
+    }
+
+    // Perform the delets.
+    $sql = "DELETE FROM {menu_router} WHERE path = '%s'";
+    foreach ($changes_delete as $path) {
+      db_query($sql, $path);
+    }
+
+    // Store the new rows in a cache for use as old rows on next rebuild.
+    // We can't store this in cache_menu, as this would get wiped almost instantlty
+    // due to a call to menu_cache_clear_all() right after the router build,
+    // meaning we could never actually use it, so store in cache instead.
+    cache_set('menu_router_build_old_rows', $rows_new);
+
     _menu_router_cache($menu);
   }
   return $menu;
@@ -2358,8 +2479,7 @@ function _menu_router_build($callbacks) 
     watchdog('php', 'Menu router rebuild failed - some paths may not work correctly.', array(), WATCHDOG_ERROR);
     return array();
   }
-  // Delete the existing router since we have some data to replace it.
-  db_query('DELETE FROM {menu_router}');
+
   // Apply inheritance rules.
   foreach ($menu as $path => $v) {
     $item = &$menu[$path];
@@ -2439,24 +2559,6 @@ function _menu_router_build($callbacks) 
       $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']);
       $item['include file'] = $file_path .'/'. $item['file'];
     }
-
-    $title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : '';
-    db_query("INSERT INTO {menu_router}
-      (path, load_functions, to_arg_functions, access_callback,
-      access_arguments, page_callback, page_arguments, fit,
-      number_parts, tab_parent, tab_root,
-      title, title_callback, title_arguments,
-      type, block_callback, description, position, weight, file)
-      VALUES ('%s', '%s', '%s', '%s',
-      '%s', '%s', '%s', %d,
-      %d, '%s', '%s',
-      '%s', '%s', '%s',
-      %d, '%s', '%s', '%s', %d, '%s')",
-      $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'],
-      serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'],
-      $item['_number_parts'], $item['tab_parent'], $item['tab_root'],
-      $item['title'], $item['title callback'], $title_arguments,
-      $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']);
   }
   // Sort the masks so they are in order of descending fit, and store them.
   $masks = array_keys($masks);
