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	20 Jan 2011 21:03: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);
+    }
+  }
+
+  $items = array();
+  foreach ($query->execute() 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;
 }
@@ -2774,10 +2808,8 @@
     ->orderBy('depth', 'DESC')
     ->execute();
   // 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($result, TRUE);
 }
 
 /**
@@ -2843,11 +2875,95 @@
   $links = menu_load_links($menu_name);
   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);
+
+    _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(array $items, $force = FALSE) {
+  if (!empty($items)) {
+    $mlids = array();
+    $parent_mlids = array();
+    $items_with_children = array();
+    $affected_menus = array();
+    foreach ($items as $key => $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);
+    }
+
+    // Update the has_children status of parents of deleted links.
+    if (!empty($parent_mlids)) {
+      db_query("UPDATE {menu_links} parent SET parent.has_children = 0 WHERE parent.has_children = 1 AND mlid IN (:parents) AND NOT EXISTS (SELECT * FROM {menu_links} child WHERE child.hidden = 0 AND child.plid = parent.mlid AND child.menu_name = parent.menu_name)", array(':parents' => $parent_mlids));
+    }
+
+    // Clear caches.
+    foreach ($affected_menus as $menu_name) {
+      menu_cache_clear($menu_name);
+    }
+    _menu_clear_page_cache();
   }
 }
 
@@ -2861,13 +2977,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))->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
+    _menu_link_delete_multiple($items);
   }
 }
 
@@ -2878,29 +2992,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	20 Jan 2011 21:03:39 -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.
    */
@@ -816,9 +816,7 @@
         ->execute();
       // 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($result, TRUE);
     }
 
     drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
