Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.1003
diff -u -r1.1003 system.module
--- modules/system/system.module	4 Jan 2011 00:56:23 -0000	1.1003
+++ modules/system/system.module	21 Jan 2011 02:04:41 -0000
@@ -2926,11 +2926,12 @@
         // append the parent menu item's title to differentiate.
         $duplicate_path = array_search($task['title'], $titles);
         if ($duplicate_path !== FALSE) {
-          if ($parent = menu_link_load($task['plid'])) {
+          $parents = menu_link_load_multiple(array($task['plid'], $admin_tasks[$duplicate_path]['plid']));
+          if (isset($parents[$task['plid']]) && $parent = $parents[$task['plid']]) {
             // Append the parent item's title to this task's title.
             $task['title'] = t('@original_title (@parent_title)', array('@original_title' => $task['title'], '@parent_title' => $parent['title']));
           }
-          if ($parent = menu_link_load($admin_tasks[$duplicate_path]['plid'])) {
+          if (isset($parents[$admin_tasks[$duplicate_path]['plid']]) && $parent = $admin_tasks[$duplicate_path]['plid']) {
             // Append the parent item's title to the duplicated task's title.
             // We use $links[$duplicate_path] in case there are triplicates.
             $admin_tasks[$duplicate_path]['title'] = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]['title'], '@parent_title' => $parent['title']));
Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.431
diff -u -r1.431 menu.inc
--- includes/menu.inc	4 Jan 2011 06:20:30 -0000	1.431
+++ includes/menu.inc	21 Jan 2011 02:04:40 -0000
@@ -2488,6 +2488,51 @@
 }
 
 /**
+ * Load multiple menu links, access checked and link translated for rendering.
+ *
+ * This function should never be called from within node_load() or any other
+ * function used as a menu object load function since an infinite recursion may
+ * occur.
+ *
+ * @param $mlids
+ *   An array of menu link IDs.
+ * @param $conditions
+ *   An associative array of conditions on the {menu_links}
+ *   table, where the keys are the database fields and the values are the
+ *   values those fields must have.
+ *
+ * @return
+ *   An array of menu links indexed by mlid.
+ *
+ * @see menu_link_load()
+ */
+function menu_link_load_multiple(array $mlids, array $conditions = array()) {
+  $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
+  $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
+  $query->fields('ml');
+  // Weight should be taken from {menu_links}, not {menu_router}.
+  $query->addField('ml', 'weight', 'link_weight');
+  $query->fields('m');
+  if (!empty($mlids)) {
+    $query->condition('ml.mlid', $mlids, 'IN');
+  }
+  if (!empty($conditions)) {
+    foreach ($conditions as $field => $value) {
+      $query->condition('ml.' . $field, $value);
+    }
+  }
+  $result = $query->execute();
+
+  $items = array();
+  foreach ($result as $item) {
+    $item['weight'] = $item['link_weight'];
+    $items[$item['mlid']] = $item;
+    _menu_link_translate($items[$item['mlid']]);
+  }
+  return $items;
+}
+
+/**
  * Get a menu link by its mlid, access checked and link translated for rendering.
  *
  * This function should never be called from within node_load() or any other
@@ -2503,18 +2548,7 @@
  */
 function menu_link_load($mlid) {
   if (is_numeric($mlid)) {
-    $query = db_select('menu_links', 'ml');
-    $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
-    $query->fields('ml');
-    // Weight should be taken from {menu_links}, not {menu_router}.
-    $query->addField('ml', 'weight', 'link_weight');
-    $query->fields('m');
-    $query->condition('ml.mlid', $mlid);
-    if ($item = $query->execute()->fetchAssoc()) {
-      $item['weight'] = $item['link_weight'];
-      _menu_link_translate($item);
-      return $item;
-    }
+    return reset(menu_link_load_multiple(array($mlid)));
   }
   return FALSE;
 }
@@ -2765,19 +2799,17 @@
     }
   }
   // Find any item whose router path does not exist any more.
-  $result = db_select('menu_links')
+  $items = db_select('menu_links')
     ->fields('menu_links')
     ->condition('router_path', $paths, 'NOT IN')
     ->condition('external', 0)
     ->condition('updated', 0)
     ->condition('customized', 0)
     ->orderBy('depth', 'DESC')
-    ->execute();
+    ->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);;
   // Remove all such items. Starting from those with the greatest depth will
-  // minimize the amount of re-parenting done by menu_link_delete().
-  foreach ($result as $item) {
-    _menu_delete_item($item, TRUE);
-  }
+  // minimize the amount of re-parenting done by _menu_link_delete_multiple().
+  _menu_link_delete_multiple($items, TRUE);
 }
 
 /**
@@ -2841,13 +2873,117 @@
  */
 function menu_delete_links($menu_name) {
   $links = menu_load_links($menu_name);
-  foreach ($links as $link) {
+  foreach ($links as &$link) {
     // To speed up the deletion process, we reset some link properties that
-    // would trigger re-parenting logic in _menu_delete_item() and
-    // _menu_update_parental_status().
+    // would trigger re-parenting logic in _menu_link_delete_multiple().
     $link['has_children'] = FALSE;
     $link['plid'] = 0;
-    _menu_delete_item($link);
+  }
+  _menu_link_delete_multiple($links);
+}
+
+/**
+ * Delete multiple menu links.
+ *
+ * @param $mlids
+ *   An array of menu link IDs.
+ *
+ * @see menu_link_delete()
+ */
+function menu_link_delete_multiple(array $mlids) {
+  if (!empty($mlids)) {
+    $items = db_select('menu_links')
+      ->fields('menu_links')
+      ->condition('mlid', $mlids, 'IN')
+      // Exclude links belonging to system module except if they are marked
+      // updated (generated during update from Drupal 5).
+      ->condition(db_or()->condition('module', 'system', '<>')->condition('updated', 0, '<>'))
+      ->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
+
+    if (!empty($items)) {
+      _menu_link_delete_multiple($items);
+    }
+  }
+}
+
+/**
+ * Helper function for menu_link_delete_multiple; deletes multiple menu links.
+ *
+ * @param $items
+ *   An array of menu links.
+ * @param $force
+ *   Forces deletion. Internal use only, setting to TRUE is discouraged.
+ *
+ * @see menu_link_delete()
+ * @see menu_link_delete_multiple()
+ */
+function _menu_link_delete_multiple($items, $force = FALSE) {
+  if (!empty($items)) {
+    $mlids = array();
+    $parent_mlids = array();
+    $items_with_children = array();
+    $affected_menus = array();
+    foreach ($items as $key => $item) {
+      if (is_object($item)) {
+        $items[$key] = get_object_vars($item);
+      }
+      if (!$force && $item['module'] == 'system' && !$item['updated']) {
+        unset ($items[$key]);
+      }
+      $mlids[$item['mlid']] = $item['mlid'];
+      $parent_mlids[$item['plid']] = $item['plid'];
+      if ($item['has_children']) {
+        $items_with_children[$item['mlid']] = $item['mlid'];
+      }
+      $affected_menus[$item['menu_name']] = $item['menu_name'];
+    }
+    // Exclude root item and items that are going to be deleted.
+    $parent_mlids = array_diff_key($parent_mlids, array(0 => 0), $mlids);
+
+    // Re-parent any children to it's closest parent that is not deleted.
+    if (!empty($items_with_children)) {
+      $children = menu_link_load_multiple(array(), array('plid' => $items_with_children));
+      foreach ($children as $item) {
+        while (isset($items[$item['plid']])) {
+          $item['plid'] = $items[$item['plid']]['plid'];
+        }
+        menu_link_save($item);
+      }
+    }
+
+    // Delete the items from the database.
+    db_delete('menu_links')->condition('mlid', $mlids, 'IN')->execute();
+
+    foreach ($items as $item) {
+      // Notify modules we have deleted the item.
+      module_invoke_all('menu_link_delete', $item);
+
+      // Check if at least one visible child exists in the table.
+      $query = db_select('menu_links');
+      $query->addField('menu_links', 'mlid');
+      $query->condition('menu_name', $item['menu_name']);
+      $query->condition('hidden', 0);
+      $query->condition('plid', $item['plid']);
+      $query->range(0, 1);
+
+      if ((bool) $query->execute()->fetchField() == FALSE) {
+        unset($parent_mlids[$item['mlid']]);
+      }
+    }
+
+    // Update the has_children status of parents of deleted links.
+    if (!empty($parent_mlids)) {
+      db_update('menu_links')
+        ->fields(array('has_children' => 0))
+        ->condition('mlid', $parent_mlids, 'IN')
+        ->execute();
+    }
+
+    // Clear caches.
+    foreach ($affected_menus as $menu_name) {
+      menu_cache_clear($menu_name);
+    }
+    _menu_clear_page_cache();
   }
 }
 
@@ -2861,13 +2997,11 @@
  */
 function menu_link_delete($mlid, $path = NULL) {
   if (isset($mlid)) {
-    _menu_delete_item(db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc());
+    menu_link_delete_multiple(array($mlid));
   }
   else {
-    $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $path));
-    foreach ($result as $link) {
-      _menu_delete_item($link);
-    }
+    $items = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $path))->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
+    _menu_link_delete_multiple($items);
   }
 }
 
@@ -2878,29 +3012,13 @@
  *   Item to be deleted.
  * @param $force
  *   Forces deletion. Internal use only, setting to TRUE is discouraged.
+ *
+ * @todo This function has become obsolete in favor of
+ * _menu_link_delete_multiple(). Remove in D8.
  */
 function _menu_delete_item($item, $force = FALSE) {
   $item = is_object($item) ? get_object_vars($item) : $item;
-  if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) {
-    // Children get re-attached to the item's parent.
-    if ($item['has_children']) {
-      $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = :plid", array(':plid' => $item['mlid']));
-      foreach ($result as $m) {
-        $child = menu_link_load($m->mlid);
-        $child['plid'] = $item['plid'];
-        menu_link_save($child);
-      }
-    }
-    db_delete('menu_links')->condition('mlid', $item['mlid'])->execute();
-
-    // Notify modules we have deleted the item.
-    module_invoke_all('menu_link_delete', $item);
-
-    // Update the has_children status of the parent.
-    _menu_update_parental_status($item);
-    menu_cache_clear($item['menu_name']);
-    _menu_clear_page_cache();
-  }
+  _menu_link_delete_multiple(array($item), $force);
 }
 
 /**
Index: includes/install.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/install.inc,v
retrieving revision 1.152
diff -u -r1.152 install.inc
--- includes/install.inc	2 Jan 2011 17:23:20 -0000	1.152
+++ includes/install.inc	21 Jan 2011 02:04:38 -0000
@@ -548,7 +548,7 @@
    *
    * @param $database
    *   An array of driver specific configuration options.
-   * 
+   *
    * @return
    *   An array of driver configuration errors, keyed by form element name.
    */
@@ -808,17 +808,15 @@
         $paths[$index] = implode('/', $parts);
       }
 
-      $result = db_select('menu_links')
+      $items = db_select('menu_links')
         ->fields('menu_links')
         ->condition('router_path', $paths, 'IN')
         ->condition('external', 0)
         ->orderBy('depth')
-        ->execute();
+        ->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
       // Remove all such items. Starting from those with the greatest depth will
       // minimize the amount of re-parenting done by menu_link_delete().
-      foreach ($result as $item) {
-        _menu_delete_item($item, TRUE);
-      }
+      _menu_link_delete_multiple($items, TRUE);
     }
 
     drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
