← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/eliminate-prejoins-where-possible into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/eliminate-prejoins-where-possible into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #696167 in Launchpad itself: "duplicate milestones in advanced person's bug search"
  https://bugs.launchpad.net/launchpad/+bug/696167

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/eliminate-prejoins-where-possible/+merge/102254

This branch removes prejoin support from bugtasksearch. It was only used for two things (the milestone listing on https://launchpad.net/~/+bugs?advanced=1 and preloading the corresponding Bug for each BugTask), and makes BugTaskFlat integration unbelievably awful.

I replaced the milestone madness with a simple load_related. It's still terribly inefficient (loading every task related to the person, I mean what?), but it's better than it was. This also happens to fix bug #696167 by not being insane.

The Bug prejoin in search() itself is replaced with a load_related(). I also replaced all the other preloading in search() with load_related(), since load_related() is awesome unlike pretty much everything else in the codebase.



-- 
https://code.launchpad.net/~wgrant/launchpad/eliminate-prejoins-where-possible/+merge/102254
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/eliminate-prejoins-where-possible into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py	2012-04-03 06:14:09 +0000
+++ lib/lp/bugs/browser/bugtask.py	2012-04-17 08:56:23 +0000
@@ -3036,7 +3036,7 @@
         return self._batch_navigator
 
     def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None, prejoins=[]):
+                        extra_params=None):
         """Return a `SelectResults` object for the GET search criteria.
 
         :param searchtext: Text that must occur in the bug report. If
@@ -3055,7 +3055,7 @@
             searchtext=searchtext, extra_params=extra_params)
         search_params.user = self.user
         try:
-            tasks = context.searchTasks(search_params, prejoins=prejoins)
+            tasks = context.searchTasks(search_params)
         except ValueError as e:
             self.request.response.addErrorNotification(str(e))
             self.request.response.redirect(canonical_url(

=== modified file 'lib/lp/bugs/browser/person.py'
--- lib/lp/bugs/browser/person.py	2012-02-28 04:24:19 +0000
+++ lib/lp/bugs/browser/person.py	2012-04-17 08:56:23 +0000
@@ -21,7 +21,6 @@
 from operator import itemgetter
 import urllib
 
-from storm.expr import Join
 from zope.component import getUtility
 from zope.schema.vocabulary import getVocabularyRegistry
 
@@ -32,12 +31,12 @@
     IBugTaskSet,
     UNRESOLVED_BUGTASK_STATUSES,
     )
-from lp.bugs.model.bugtask import BugTask
 from lp.registry.interfaces.person import IPerson
 from lp.registry.model.milestone import (
     Milestone,
     milestone_sort_key,
     )
+from lp.services.database.bulk import load_related
 from lp.services.feeds.browser import FeedsMixin
 from lp.services.helpers import shortlist
 from lp.services.propertycache import cachedproperty
@@ -142,12 +141,10 @@
 
     def getMilestoneWidgetValues(self):
         """Return data used to render the milestone checkboxes."""
-        prejoins = [
-            (Milestone, Join(Milestone, BugTask.milestone == Milestone.id))]
-        milestones = [
-            bugtask.milestone
-            for bugtask in self.searchUnbatched(prejoins=prejoins)]
-        milestones = sorted(milestones, key=milestone_sort_key, reverse=True)
+        tasks = self.searchUnbatched()
+        milestones = sorted(
+            load_related(Milestone, tasks, ['milestoneID']),
+            key=milestone_sort_key, reverse=True)
         return [
             dict(title=milestone.title, value=milestone.id, checked=False)
             for milestone in milestones]
@@ -363,7 +360,7 @@
     view_name = '+assignedbugs'
 
     def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None, prejoins=[]):
+                        extra_params=None):
         """Return the open bugs assigned to a person."""
         if context is None:
             context = self.context
@@ -375,8 +372,7 @@
         extra_params['assignee'] = context
 
         sup = super(PersonAssignedBugTaskSearchListingView, self)
-        return sup.searchUnbatched(
-            searchtext, context, extra_params, prejoins)
+        return sup.searchUnbatched(searchtext, context, extra_params)
 
     def shouldShowAssigneeWidget(self):
         """Should the assignee widget be shown on the advanced search page?"""
@@ -421,7 +417,7 @@
     page_title = 'Commented bugs'
 
     def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None, prejoins=[]):
+                        extra_params=None):
         """Return the open bugs commented on by a person."""
         if context is None:
             context = self.context
@@ -433,8 +429,7 @@
         extra_params['bug_commenter'] = context
 
         sup = super(PersonCommentedBugTaskSearchListingView, self)
-        return sup.searchUnbatched(
-            searchtext, context, extra_params, prejoins)
+        return sup.searchUnbatched(searchtext, context, extra_params)
 
     @property
     def context_description(self):
@@ -468,7 +463,7 @@
     page_title = 'Bugs affecting'   # The context is added externally.
 
     def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None, prejoins=[]):
+                        extra_params=None):
         """Return the open bugs assigned to a person."""
         if context is None:
             context = self.context
@@ -480,8 +475,7 @@
         extra_params['affected_user'] = context
 
         sup = super(PersonAffectingBugTaskSearchListingView, self)
-        return sup.searchUnbatched(
-            searchtext, context, extra_params, prejoins)
+        return sup.searchUnbatched(searchtext, context, extra_params)
 
     def shouldShowAssigneeWidget(self):
         """Should the assignee widget be shown on the advanced search page?"""
@@ -527,7 +521,7 @@
     page_title = 'Related bugs'
 
     def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None, prejoins=[]):
+                        extra_params=None):
         """Return the open bugs related to a person.
 
         :param extra_params: A dict that provides search params added to
@@ -557,8 +551,7 @@
             commenter_params.bug_commenter = context
 
         return context.searchTasks(
-            assignee_params, subscriber_params, owner_params,
-            commenter_params, prejoins=prejoins)
+            assignee_params, subscriber_params, owner_params, commenter_params)
 
     @property
     def context_description(self):
@@ -588,7 +581,7 @@
     page_title = 'Reported bugs'
 
     def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None, prejoins=[]):
+                        extra_params=None):
         """Return the bugs reported by a person."""
         if context is None:
             context = self.context
@@ -603,8 +596,7 @@
         extra_params['bug_reporter'] = context
 
         sup = super(PersonReportedBugTaskSearchListingView, self)
-        return sup.searchUnbatched(
-            searchtext, context, extra_params, prejoins)
+        return sup.searchUnbatched(searchtext, context, extra_params)
 
     @property
     def context_description(self):
@@ -646,7 +638,7 @@
     view_name = '+subscribedbugs'
 
     def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None, prejoins=[]):
+                        extra_params=None):
         """Return the bugs subscribed to by a person."""
         if context is None:
             context = self.context
@@ -658,8 +650,7 @@
         extra_params['subscriber'] = context
 
         sup = super(PersonSubscribedBugTaskSearchListingView, self)
-        return sup.searchUnbatched(
-            searchtext, context, extra_params, prejoins)
+        return sup.searchUnbatched(searchtext, context, extra_params)
 
     def shouldShowTeamPortlet(self):
         """Should the team subscribed bugs portlet be shown?"""

=== modified file 'lib/lp/bugs/browser/tests/test_buglisting.py'
--- lib/lp/bugs/browser/tests/test_buglisting.py	2012-02-07 10:43:49 +0000
+++ lib/lp/bugs/browser/tests/test_buglisting.py	2012-04-17 08:56:23 +0000
@@ -150,26 +150,6 @@
                       find_tag_by_id(browser.contents, 'portlet-tags'),
                       "portlet-tags should not be shown.")
 
-    def test_searchUnbatched_can_preload_objects(self):
-        # BugTaskSearchListingView.searchUnbatched() can optionally
-        # preload objects while retrieving the bugtasks.
-        product = self.factory.makeProduct()
-        bugtask_1 = self.factory.makeBug(product=product).default_bugtask
-        bugtask_2 = self.factory.makeBug(product=product).default_bugtask
-        view = create_initialized_view(product, '+bugs')
-        Store.of(product).invalidate()
-        with StormStatementRecorder() as recorder:
-            prejoins = [
-                (Person, LeftJoin(Person, BugTask.owner == Person.id)),
-                ]
-            bugtasks = list(view.searchUnbatched(prejoins=prejoins))
-            self.assertEqual(
-                [bugtask_1, bugtask_2], bugtasks)
-            # If the table prejoin failed, then this will issue two
-            # additional SQL queries
-            [bugtask.owner for bugtask in bugtasks]
-        self.assertThat(recorder, HasQueryCount(Equals(2)))
-
     def test_search_components_error(self):
         # Searching for using components for bug targets that are not a distro
         # or distroseries will report an error, but not OOPS.  See bug

=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py	2012-02-08 06:00:46 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py	2012-04-17 08:56:23 +0000
@@ -252,7 +252,7 @@
                     hardware_owner_is_subscribed_to_bug=False,
                     hardware_is_linked_to_bug=False, linked_branches=None,
                     linked_blueprints=None, structural_subscriber=None,
-                    modified_since=None, created_since=None, prejoins=[]):
+                    modified_since=None, created_since=None):
         """Search the IBugTasks reported on this entity.
 
         :search_params: a BugTaskSearchParams object

=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py	2012-03-22 19:36:38 +0000
+++ lib/lp/bugs/interfaces/bugtask.py	2012-04-17 08:56:23 +0000
@@ -1531,9 +1531,6 @@
 
         :param search_params: a BugTaskSearchParams object
         :param args: any number of BugTaskSearchParams objects
-        :param prejoins: (keyword) A sequence of tuples
-            (table, table_join) which should be pre-joined in addition
-            to the default prejoins.
 
         If more than one BugTaskSearchParams is given, return the union of
         IBugTasks which match any of them, with the results ordered by the

=== modified file 'lib/lp/bugs/interfaces/malone.py'
--- lib/lp/bugs/interfaces/malone.py	2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/interfaces/malone.py	2012-04-17 08:56:23 +0000
@@ -33,7 +33,7 @@
     """Application root for malone."""
     export_as_webservice_collection(IBug)
 
-    def searchTasks(search_params, prejoins=[]):
+    def searchTasks(search_params):
         """Search IBugTasks with the given search parameters."""
 
     bug_count = Attribute("The number of bugs recorded in Launchpad")

=== modified file 'lib/lp/bugs/model/bugtarget.py'
--- lib/lp/bugs/model/bugtarget.py	2012-02-08 06:00:46 +0000
+++ lib/lp/bugs/model/bugtarget.py	2012-04-17 08:56:23 +0000
@@ -81,7 +81,7 @@
                     hardware_owner_is_subscribed_to_bug=False,
                     hardware_is_linked_to_bug=False, linked_branches=None,
                     linked_blueprints=None, modified_since=None,
-                    created_since=None, prejoins=[]):
+                    created_since=None):
         """See `IHasBugs`."""
         if status is None:
             # If no statuses are supplied, default to the
@@ -97,10 +97,9 @@
             del kwargs['self']
             del kwargs['user']
             del kwargs['search_params']
-            del kwargs['prejoins']
             search_params = BugTaskSearchParams.fromSearchForm(user, **kwargs)
         self._customizeSearchParams(search_params)
-        return BugTaskSet().search(search_params, prejoins=prejoins)
+        return BugTaskSet().search(search_params)
 
     def _customizeSearchParams(self, search_params):
         """Customize `search_params` for a specific target."""

=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py	2012-04-12 10:12:59 +0000
+++ lib/lp/bugs/model/bugtask.py	2012-04-17 08:56:23 +0000
@@ -112,6 +112,7 @@
 from lp.registry.model.pillar import pillar_sort_key
 from lp.registry.model.sourcepackagename import SourcePackageName
 from lp.services import features
+from lp.services.database.bulk import load_related
 from lp.services.database.constants import UTC_NOW
 from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.enumcol import EnumCol
@@ -1504,9 +1505,6 @@
         :param _noprejoins: Private internal parameter to BugTaskSet which
             disables all use of prejoins : consolidated from code paths that
             claim they were inefficient and unwanted.
-        :param prejoins: A sequence of tuples (table, table_join) which
-            which should be pre-joined in addition to the default prejoins.
-            This parameter has no effect if _noprejoins is True.
         """
         # Prevent circular import problems.
         from lp.registry.model.product import Product
@@ -1514,41 +1512,18 @@
         from lp.bugs.model.bugtasksearch import search_bugs
         _noprejoins = kwargs.get('_noprejoins', False)
         if _noprejoins:
-            prejoins = []
-            resultrow = BugTask
             eager_load = None
         else:
-            requested_joins = kwargs.get('prejoins', [])
-            # NB: We could save later work by predicting what sort of
-            # targets we might be interested in here, but as at any
-            # point we're dealing with relatively few results, this is
-            # likely to be a small win.
-            prejoins = [
-                (Bug, Join(Bug, BugTask.bug == Bug.id))] + requested_joins
-
-            def eager_load(results):
-                product_ids = set([row[0].productID for row in results])
-                product_ids.discard(None)
-                pkgname_ids = set(
-                    [row[0].sourcepackagenameID for row in results])
-                pkgname_ids.discard(None)
-                store = IStore(BugTask)
-                if product_ids:
-                    list(store.find(Product, Product.id.is_in(product_ids)))
-                if pkgname_ids:
-                    list(store.find(SourcePackageName,
-                        SourcePackageName.id.is_in(pkgname_ids)))
-            resultrow = (BugTask, Bug)
-            additional_result_objects = [
-                table for table, join in requested_joins
-                if table not in resultrow]
-            resultrow = resultrow + tuple(additional_result_objects)
-        return search_bugs(resultrow, prejoins, eager_load, (params,) + args)
+            def eager_load(rows):
+                load_related(Bug, rows, ['bugID'])
+                load_related(Product, rows, ['productID'])
+                load_related(SourcePackageName, rows, ['sourcepackagenameID'])
+        return search_bugs(BugTask, eager_load, (params,) + args)
 
     def searchBugIds(self, params):
         """See `IBugTaskSet`."""
         from lp.bugs.model.bugtasksearch import search_bugs
-        return search_bugs(BugTask.bugID, [], None, [params]).result_set
+        return search_bugs(BugTask.bugID, None, [params]).result_set
 
     def countBugs(self, user, contexts, group_on):
         """See `IBugTaskSet`."""

=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
--- lib/lp/bugs/model/bugtasksearch.py	2012-04-13 05:32:26 +0000
+++ lib/lp/bugs/model/bugtasksearch.py	2012-04-17 08:56:23 +0000
@@ -201,12 +201,10 @@
         return comp == None
 
 
-def search_bugs(resultrow, prejoins, pre_iter_hook, alternatives):
+def search_bugs(resultrow, pre_iter_hook, alternatives):
     """Return a Storm result set for the given search parameters.
 
     :param resultrow: The type of data returned by the query.
-    :param prejoins: A sequence of Storm SQL row instances which are
-        pre-joined.
     :param pre_iter_hook: An optional pre-iteration hook used for eager
         loading bug targets for list views.
     :param alternatives: A sequence of BugTaskSearchParams instances, the
@@ -225,14 +223,13 @@
         decorators.append(bugtask_decorator)
 
         if has_duplicate_results:
-            origin = _build_origin(join_tables, [], clauseTables)
-            outer_origin = _build_origin(orderby_joins, prejoins, [])
+            origin = _build_origin(join_tables, clauseTables)
+            outer_origin = _build_origin(orderby_joins, [])
             subquery = Select(BugTask.id, where=query, tables=origin)
             result = store.using(*outer_origin).find(
                 resultrow, In(BugTask.id, subquery))
         else:
-            origin = _build_origin(
-                join_tables + orderby_joins, prejoins, clauseTables)
+            origin = _build_origin(join_tables + orderby_joins, clauseTables)
             result = store.using(*origin).find(resultrow, query)
     else:
         results = []
@@ -240,7 +237,7 @@
         for params in alternatives:
             [query, clauseTables, decorator, join_tables,
              has_duplicate_results, with_clause] = _build_query(params)
-            origin = _build_origin(join_tables, [], clauseTables)
+            origin = _build_origin(join_tables, clauseTables)
             localstore = store
             if with_clause:
                 localstore = store.with_(with_clause)
@@ -253,13 +250,10 @@
 
         resultset = reduce(lambda l, r: l.union(r), results)
         origin = _build_origin(
-            orderby_joins, prejoins, [],
+            orderby_joins, [],
             start_with=Alias(resultset._get_select(), "BugTask"))
         result = store.using(*origin).find(resultrow)
 
-    if prejoins:
-        decorators.insert(0, itemgetter(0))
-
     result.order_by(orderby_expression)
     return DecoratedResultSet(
         result,
@@ -267,25 +261,20 @@
         pre_iter_hook=pre_iter_hook)
 
 
-def _build_origin(join_tables, prejoin_tables, clauseTables,
-                start_with=BugTask):
+def _build_origin(join_tables, clauseTables, start_with=BugTask):
     """Build the parameter list for Store.using().
 
     :param join_tables: A sequence of tables that should be joined
         as returned by _build_query(). Each element has the form
         (table, join), where table is the table to join and join
         is a Storm Join or LeftJoin instance.
-    :param prejoin_tables: A sequence of tables that should additionally
-        be joined. Each element has the form (table, join),
-        where table is the table to join and join is a Storm Join
-        or LeftJoin instance.
     :param clauseTables: A sequence of tables that should appear in
         the FROM clause of a query. The join condition is defined in
         the WHERE clause.
 
-    Tables may appear simultaneously in join_tables, prejoin_tables
-    and in clauseTables. This method ensures that each table
-    appears exactly once in the returned sequence.
+    Tables may appear simultaneously in join_tables and in clauseTables.
+    This method ensures that each table appears exactly once in the
+    returned sequence.
     """
     origin = [start_with]
     already_joined = set(origin)
@@ -294,10 +283,6 @@
             origin.append(join)
             if table is not None:
                 already_joined.add(table)
-    for table, join in prejoin_tables:
-        if table not in already_joined:
-            origin.append(join)
-            already_joined.add(table)
     for table in clauseTables:
         if table not in already_joined:
             origin.append(table)

=== modified file 'lib/lp/bugs/tests/test_bugtarget.py'
--- lib/lp/bugs/tests/test_bugtarget.py	2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/tests/test_bugtarget.py	2012-04-17 08:56:23 +0000
@@ -16,33 +16,25 @@
 import random
 import unittest
 
-from storm.expr import LeftJoin
-from storm.store import Store
-from testtools.matchers import Equals
 from zope.component import getUtility
 
 from lp.bugs.interfaces.bug import CreateBugParams
 from lp.bugs.interfaces.bugtask import (
-    BugTaskSearchParams,
     BugTaskStatus,
     IBugTaskSet,
     )
-from lp.bugs.model.bugtask import BugTask
 from lp.registry.interfaces.distribution import (
     IDistribution,
     IDistributionSet,
     )
 from lp.registry.interfaces.product import IProductSet
 from lp.registry.interfaces.projectgroup import IProjectGroupSet
-from lp.registry.model.milestone import Milestone
 from lp.services.webapp.interfaces import ILaunchBag
 from lp.testing import (
     person_logged_in,
-    StormStatementRecorder,
     TestCaseWithFactory,
     )
 from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.matchers import HasQueryCount
 from lp.testing.systemdocs import (
     LayeredDocFileSuite,
     setUp,
@@ -208,101 +200,6 @@
         self.assertTrue(bug.canBeAQuestion())
 
 
-class TestBugTargetSearchTasks(TestCaseWithFactory):
-    """Tests of IHasBugs.searchTasks()."""
-
-    layer = DatabaseFunctionalLayer
-
-    def setUp(self):
-        super(TestBugTargetSearchTasks, self).setUp()
-        self.bug = self.factory.makeBug()
-        self.target = self.bug.default_bugtask.target
-        self.milestone = self.factory.makeMilestone(product=self.target)
-        with person_logged_in(self.target.owner):
-            self.bug.default_bugtask.transitionToMilestone(
-                self.milestone, self.target.owner)
-        self.store = Store.of(self.bug)
-        self.store.flush()
-        self.store.invalidate()
-
-    def test_preload_other_objects(self):
-        # We can prejoin objects in calls of searchTasks().
-
-        # Without prejoining the table Milestone, accessing the
-        # BugTask property milestone requires an extra query.
-        with StormStatementRecorder() as recorder:
-            params = BugTaskSearchParams(user=None)
-            found_tasks = self.target.searchTasks(params)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(2)))
-
-        # When we prejoin Milestone, the milestone of our bugtask is
-        # already loaded during the main search query.
-        self.store.invalidate()
-        with StormStatementRecorder() as recorder:
-            params = BugTaskSearchParams(user=None)
-            prejoins = [(Milestone,
-                         LeftJoin(Milestone,
-                                  BugTask.milestone == Milestone.id))]
-            found_tasks = self.target.searchTasks(params, prejoins=prejoins)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(1)))
-
-    def test_preload_other_objects_for_person_search_no_params_passed(self):
-        # We can prejoin objects in calls of Person.searchTasks().
-        owner = self.bug.owner
-        with StormStatementRecorder() as recorder:
-            found_tasks = owner.searchTasks(None, user=None)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(2)))
-
-        self.store.invalidate()
-        with StormStatementRecorder() as recorder:
-            prejoins = [(Milestone,
-                         LeftJoin(Milestone,
-                                  BugTask.milestone == Milestone.id))]
-            found_tasks = owner.searchTasks(
-                None, user=None, prejoins=prejoins)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(1)))
-
-    def test_preload_other_objects_for_person_search_no_keywords_passed(self):
-        # We can prejoin objects in calls of Person.searchTasks().
-        owner = self.bug.owner
-        params = BugTaskSearchParams(user=None, owner=owner)
-        with StormStatementRecorder() as recorder:
-            found_tasks = owner.searchTasks(params)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(2)))
-
-        self.store.invalidate()
-        with StormStatementRecorder() as recorder:
-            prejoins = [(Milestone,
-                         LeftJoin(Milestone,
-                                  BugTask.milestone == Milestone.id))]
-            found_tasks = owner.searchTasks(params, prejoins=prejoins)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(1)))
-
-    def test_preload_other_objects_for_person_search_keywords_passed(self):
-        # We can prejoin objects in calls of Person.searchTasks().
-        owner = self.bug.owner
-        params = BugTaskSearchParams(user=None, owner=owner)
-        with StormStatementRecorder() as recorder:
-            found_tasks = owner.searchTasks(params, order_by=BugTask.id)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(2)))
-
-        self.store.invalidate()
-        with StormStatementRecorder() as recorder:
-            prejoins = [(Milestone,
-                         LeftJoin(Milestone,
-                                  BugTask.milestone == Milestone.id))]
-            found_tasks = owner.searchTasks(params, prejoins=prejoins)
-            found_tasks[0].milestone
-        self.assertThat(recorder, HasQueryCount(Equals(1)))
-
-
 def test_suite():
     """Return the `IBugTarget` TestSuite."""
     suite = unittest.TestSuite()

=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py	2012-04-12 22:50:15 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py	2012-04-17 08:56:23 +0000
@@ -10,9 +10,6 @@
 import unittest
 
 import pytz
-from storm.expr import Join
-from storm.store import Store
-from testtools.matchers import Equals
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
@@ -26,7 +23,6 @@
     IBugTaskSet,
     )
 from lp.bugs.model.bugsummary import BugSummary
-from lp.bugs.model.bugtask import BugTask
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
@@ -35,7 +31,6 @@
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.sourcepackage import ISourcePackage
-from lp.registry.model.person import Person
 from lp.services.searchbuilder import (
     all,
     any,
@@ -43,14 +38,12 @@
     )
 from lp.testing import (
     person_logged_in,
-    StormStatementRecorder,
     TestCaseWithFactory,
     )
 from lp.testing.layers import (
     DatabaseFunctionalLayer,
     LaunchpadFunctionalLayer,
     )
-from lp.testing.matchers import HasQueryCount
 
 
 class SearchTestBase:
@@ -1505,43 +1498,13 @@
 class PreloadBugtaskTargets(MultipleParams):
     """Preload bug targets during a BugTaskSet.search() query."""
 
-    def runSearch(self, params, *args, **kw):
+    def runSearch(self, params, *args):
         """Run BugTaskSet.search() and preload bugtask target objects."""
-        return list(self.bugtask_set.search(
-            params, *args, _noprejoins=False, **kw))
+        return list(self.bugtask_set.search(params, *args, _noprejoins=False))
 
     def resultValuesForBugtasks(self, expected_bugtasks):
         return expected_bugtasks
 
-    def test_preload_additional_objects(self):
-        # It is possible to join additional tables in the search query
-        # in order to load related Storm objects during the query.
-        store = Store.of(self.bugtasks[0])
-        store.invalidate()
-
-        # If we do not prejoin the owner, two queries a run
-        # in order to retrieve the owner of the bugtask.
-        with StormStatementRecorder() as recorder:
-            params = self.getBugTaskSearchParams(user=None)
-            found_tasks = self.runSearch(params)
-            found_tasks[0].owner
-            self.assertTrue(len(recorder.statements) > 1)
-
-        # If we join the table person on bugtask.owner == person.id
-        # the owner object is loaded in the query that retrieves the
-        # bugtasks.
-        store.invalidate()
-        with StormStatementRecorder() as recorder:
-            params = self.getBugTaskSearchParams(user=None)
-            found_tasks = self.runSearch(
-                params,
-                prejoins=[(Person, Join(Person, BugTask.owner == Person.id))])
-            # More than one query may have been performed
-            search_count = recorder.count
-            # Accessing the owner does not trigger more queries.
-            found_tasks[0].owner
-            self.assertThat(recorder, HasQueryCount(Equals(search_count)))
-
 
 class NoPreloadBugtaskTargets(MultipleParams):
     """Do not preload bug targets during a BugTaskSet.search() query."""

=== modified file 'lib/lp/registry/browser/tests/test_person.py'
--- lib/lp/registry/browser/tests/test_person.py	2012-02-28 04:24:19 +0000
+++ lib/lp/registry/browser/tests/test_person.py	2012-04-17 08:56:23 +0000
@@ -1135,16 +1135,6 @@
         self.assertEqual(
             self.expected_for_search_unbatched, list(view.searchUnbatched()))
 
-    def test_searchUnbatched_with_prejoins(self):
-        view = create_initialized_view(self.person, self.view_name)
-        Store.of(self.subscribed_bug).invalidate()
-        with StormStatementRecorder() as recorder:
-            prejoins = [
-                (Person, LeftJoin(Person, BugTask.owner == Person.id))]
-            bugtasks = view.searchUnbatched(prejoins=prejoins)
-            [bugtask.owner for bugtask in bugtasks]
-        self.assertThat(recorder, HasQueryCount(LessThan(3)))
-
     def test_getMilestoneWidgetValues(self):
         view = create_initialized_view(self.person, self.view_name)
         milestones = [

=== modified file 'lib/lp/systemhomes.py'
--- lib/lp/systemhomes.py	2012-03-23 06:10:28 +0000
+++ lib/lp/systemhomes.py	2012-04-17 08:56:23 +0000
@@ -119,10 +119,9 @@
     def __init__(self):
         self.title = 'Malone: the Launchpad bug tracker'
 
-    def searchTasks(self, search_params, prejoins=[]):
+    def searchTasks(self, search_params):
         """See `IMaloneApplication`."""
-        return getUtility(IBugTaskSet).search(
-            search_params, prejoins=prejoins)
+        return getUtility(IBugTaskSet).search(search_params)
 
     def createBug(self, owner, title, description, target,
                   security_related=False, private=False, tags=None):