← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/bugtaskflat-by-default into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/bugtaskflat-by-default into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/bugtaskflat-by-default/+merge/104215

This branch is a fairly straightforward obliteration of the non-BugTaskFlat bug search mechanism. A few query counts needed incrementing to cope with the extra BugTask selection query.
-- 
https://code.launchpad.net/~wgrant/launchpad/bugtaskflat-by-default/+merge/104215
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/bugtaskflat-by-default into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py	2012-04-27 05:56:48 +0000
+++ lib/lp/bugs/browser/bugtask.py	2012-05-01 07:46:21 +0000
@@ -217,7 +217,7 @@
 from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus
 from lp.bugs.interfaces.cve import ICveSet
 from lp.bugs.interfaces.malone import IMaloneApplication
-from lp.bugs.model.bugtasksearch import unflat_orderby_expression
+from lp.bugs.model.bugtasksearch import orderby_expression
 from lp.code.interfaces.branchcollection import IAllBranches
 from lp.layers import FeedsLayer
 from lp.registry.enums import InformationType
@@ -2802,7 +2802,7 @@
                 orderby_col = orderby_col[1:]
 
             try:
-                unflat_orderby_expression[orderby_col]
+                orderby_expression[orderby_col]
             except KeyError:
                 raise UnexpectedFormData(
                     "Unknown sort column '%s'" % orderby_col)

=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
--- lib/lp/bugs/browser/tests/test_bugtask.py	2012-04-18 03:56:54 +0000
+++ lib/lp/bugs/browser/tests/test_bugtask.py	2012-05-01 07:46:21 +0000
@@ -52,7 +52,7 @@
     IBugTask,
     IBugTaskSet,
     )
-from lp.bugs.model.bugtasksearch import unflat_orderby_expression
+from lp.bugs.model.bugtasksearch import orderby_expression
 from lp.layers import (
     FeedsLayer,
     setFirstLayer,
@@ -139,7 +139,7 @@
         self.getUserBrowser(url, person_no_teams)
         # This may seem large: it is; there is easily another 30% fat in
         # there.
-        self.assertThat(recorder, HasQueryCount(LessThan(86)))
+        self.assertThat(recorder, HasQueryCount(LessThan(88)))
         count_with_no_teams = recorder.count
         # count with many teams
         self.invalidate_caches(task)
@@ -155,7 +155,7 @@
     def test_rendered_query_counts_constant_with_attachments(self):
         with celebrity_logged_in('admin'):
             browses_under_limit = BrowsesWithQueryLimit(
-                89, self.factory.makePerson())
+                91, self.factory.makePerson())
 
             # First test with a single attachment.
             task = self.factory.makeBugTask()
@@ -200,7 +200,7 @@
         self.invalidate_caches(task)
         self.getUserBrowser(url, owner)
         # At least 20 of these should be removed.
-        self.assertThat(recorder, HasQueryCount(LessThan(108)))
+        self.assertThat(recorder, HasQueryCount(LessThan(110)))
         count_with_no_branches = recorder.count
         for sp in sourcepackages:
             self.makeLinkedBranchMergeProposal(sp, bug, owner)
@@ -2325,7 +2325,7 @@
         cache = IJSONRequestCache(view.request)
         json_sort_keys = cache.objects['sort_keys']
         json_sort_keys = set(key[0] for key in json_sort_keys)
-        valid_keys = set(unflat_orderby_expression.keys())
+        valid_keys = set(orderby_expression.keys())
         self.assertEqual(
             valid_keys, json_sort_keys,
             "Existing sort order values not available in JSON cache: %r; "

=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
--- lib/lp/bugs/model/bugtasksearch.py	2012-04-27 06:30:48 +0000
+++ lib/lp/bugs/model/bugtasksearch.py	2012-05-01 07:46:21 +0000
@@ -5,7 +5,7 @@
 
 __all__ = [
     'get_bug_privacy_filter',
-    'unflat_orderby_expression',
+    'orderby_expression',
     'search_bugs',
     ]
 
@@ -81,7 +81,6 @@
     Array,
     NullCount,
     )
-from lp.services.features import getFeatureFlag
 from lp.services.propertycache import get_property_cache
 from lp.services.searchbuilder import (
     all,
@@ -94,37 +93,7 @@
 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
 
 
-# This abstracts most of the columns involved in search so we can switch
-# to/from BugTaskFlat easily.
-unflat_cols = {
-    'Bug.id': Bug.id,
-    'Bug.duplicateof': Bug.duplicateof,
-    'Bug.owner': Bug.owner,
-    'Bug.date_last_updated': Bug.date_last_updated,
-    'BugTask.id': BugTask.id,
-    'BugTask.bug': BugTask.bug,
-    'BugTask.bugID': BugTask.bugID,
-    'BugTask.importance': BugTask.importance,
-    'BugTask.product': BugTask.product,
-    'BugTask.productID': BugTask.productID,
-    'BugTask.productseries': BugTask.productseries,
-    'BugTask.productseriesID': BugTask.productseriesID,
-    'BugTask.distribution': BugTask.distribution,
-    'BugTask.distributionID': BugTask.distributionID,
-    'BugTask.distroseries': BugTask.distroseries,
-    'BugTask.distroseriesID': BugTask.distroseriesID,
-    'BugTask.sourcepackagename': BugTask.sourcepackagename,
-    'BugTask.sourcepackagenameID': BugTask.sourcepackagenameID,
-    'BugTask.milestone': BugTask.milestone,
-    'BugTask.milestoneID': BugTask.milestoneID,
-    'BugTask.assignee': BugTask.assignee,
-    'BugTask.owner': BugTask.owner,
-    'BugTask.date_closed': BugTask.date_closed,
-    'BugTask.datecreated': BugTask.datecreated,
-    'BugTask._status': BugTask._status,
-    }
-
-flat_cols = {
+cols = {
     'Bug.id': BugTaskFlat.bug_id,
     'Bug.duplicateof': BugTaskFlat.duplicateof,
     'Bug.owner': BugTaskFlat.bug_owner,
@@ -153,90 +122,12 @@
     }
 
 
-bug_join = (Bug, Join(Bug, BugTask.bug == Bug.id))
 Assignee = ClassAlias(Person)
 Reporter = ClassAlias(Person)
-unflat_orderby_expression = {
-    "task": (BugTask.id, []),
-    "id": (BugTask.bugID, []),
-    "importance": (BugTask.importance, []),
-    # TODO: sort by their name?
-    "assignee": (
-        Assignee.name,
-        [
-            (Assignee,
-                LeftJoin(Assignee, BugTask.assignee == Assignee.id))
-            ]),
-    "targetname": (BugTask.targetnamecache, []),
-    "status": (BugTask._status, []),
-    "title": (Bug.title, [bug_join]),
-    "milestone": (BugTask.milestoneID, []),
-    "dateassigned": (BugTask.date_assigned, []),
-    "datecreated": (BugTask.datecreated, []),
-    "date_last_updated": (Bug.date_last_updated, [bug_join]),
-    "date_closed": (BugTask.date_closed, []),
-    "number_of_duplicates": (Bug.number_of_duplicates, [bug_join]),
-    "message_count": (Bug.message_count, [bug_join]),
-    "users_affected_count": (Bug.users_affected_count, [bug_join]),
-    "heat": (BugTask.heat, []),
-    "latest_patch_uploaded": (Bug.latest_patch_uploaded, [bug_join]),
-    "milestone_name": (
-        Milestone.name,
-        [
-            (Milestone,
-                LeftJoin(Milestone,
-                        BugTask.milestone == Milestone.id))
-            ]),
-    "reporter": (
-        Reporter.name,
-        [
-            bug_join,
-            (Reporter, Join(Reporter, Bug.owner == Reporter.id))
-            ]),
-    "tag": (
-        BugTag.tag,
-        [
-            bug_join,
-            (BugTag,
-                LeftJoin(
-                    BugTag,
-                    BugTag.bug == Bug.id and
-                    # We want at most one tag per bug. Select the
-                    # tag that comes first in alphabetic order.
-                    BugTag.id == Select(
-                        BugTag.id, tables=[BugTag],
-                        where=(BugTag.bugID == Bug.id),
-                        order_by=BugTag.tag, limit=1))),
-            ]
-        ),
-    "specification": (
-        Specification.name,
-        [
-            bug_join,
-            (Specification,
-                LeftJoin(
-                    Specification,
-                    # We want at most one specification per bug.
-                    # Select the specification that comes first
-                    # in alphabetic order.
-                    Specification.id == Select(
-                        Specification.id,
-                        tables=[
-                            SpecificationBug,
-                            Join(
-                                Specification,
-                                Specification.id ==
-                                    SpecificationBug.specificationID)],
-                        where=(SpecificationBug.bugID == Bug.id),
-                        order_by=Specification.name, limit=1))),
-            ]
-        ),
-    }
-
-flat_bug_join = (Bug, Join(Bug, Bug.id == BugTaskFlat.bug_id))
-flat_bugtask_join = (
+bug_join = (Bug, Join(Bug, Bug.id == BugTaskFlat.bug_id))
+bugtask_join = (
     BugTask, Join(BugTask, BugTask.id == BugTaskFlat.bugtask_id))
-flat_orderby_expression = {
+orderby_expression = {
     "task": (BugTaskFlat.bugtask_id, []),
     "id": (BugTaskFlat.bug_id, []),
     "importance": (BugTaskFlat.importance, []),
@@ -247,19 +138,19 @@
             (Assignee,
                 LeftJoin(Assignee, BugTaskFlat.assignee == Assignee.id))
             ]),
-    "targetname": (BugTask.targetnamecache, [flat_bugtask_join]),
+    "targetname": (BugTask.targetnamecache, [bugtask_join]),
     "status": (BugTaskFlat.status, []),
-    "title": (Bug.title, [flat_bug_join]),
+    "title": (Bug.title, [bug_join]),
     "milestone": (BugTaskFlat.milestone_id, []),
-    "dateassigned": (BugTask.date_assigned, [flat_bugtask_join]),
+    "dateassigned": (BugTask.date_assigned, [bugtask_join]),
     "datecreated": (BugTaskFlat.datecreated, []),
     "date_last_updated": (BugTaskFlat.date_last_updated, []),
-    "date_closed": (BugTask.date_closed, [flat_bugtask_join]),
-    "number_of_duplicates": (Bug.number_of_duplicates, [flat_bug_join]),
-    "message_count": (Bug.message_count, [flat_bug_join]),
-    "users_affected_count": (Bug.users_affected_count, [flat_bug_join]),
+    "date_closed": (BugTask.date_closed, [bugtask_join]),
+    "number_of_duplicates": (Bug.number_of_duplicates, [bug_join]),
+    "message_count": (Bug.message_count, [bug_join]),
+    "users_affected_count": (Bug.users_affected_count, [bug_join]),
     "heat": (BugTaskFlat.heat, []),
-    "latest_patch_uploaded": (Bug.latest_patch_uploaded, [flat_bug_join]),
+    "latest_patch_uploaded": (Bug.latest_patch_uploaded, [bug_join]),
     "milestone_name": (
         Milestone.name,
         [
@@ -348,38 +239,29 @@
         respected.
     :param just_bug_ids: Return a ResultSet of bug IDs instead of BugTasks.
     """
-    use_flat = bool(getFeatureFlag('bugs.bugtaskflat.search.enabled'))
-
     store = IStore(BugTask)
-    orderby_expression, orderby_joins = _process_order_by(
-        alternatives[0], use_flat)
+    orderby_expression, orderby_joins = _process_order_by(alternatives[0])
     decorators = []
 
-    # If we are to use BugTaskFlat, we just return the ID. The
-    # DecoratedResultSet will turn it into the actual BugTask.
-    # If we're not using BugTaskFlat yet, we should still return
-    # the BugTask directly.
-    if use_flat:
-        start = BugTaskFlat
-        if just_bug_ids:
-            want = BugTaskFlat.bug_id
-        else:
-            want = BugTaskFlat.bugtask_id
-            decorators.append(lambda id: IStore(BugTask).get(BugTask, id))
-            orig_pre_iter_hook = pre_iter_hook
-
-            def pre_iter_hook(rows):
-                rows = load(BugTask, rows)
-                if orig_pre_iter_hook:
-                    orig_pre_iter_hook(rows)
+    # Normally we just return the ID -- the DecoratedResultSet will turn
+    # it into the actual BugTask. But the caller can also request to
+    # just get the bug IDs back, in which case we don't use DRS.
+    start = BugTaskFlat
+    if just_bug_ids:
+        want = BugTaskFlat.bug_id
     else:
-        start = BugTask
-        want = BugTask.bugID if just_bug_ids else BugTask
+        want = BugTaskFlat.bugtask_id
+        decorators.append(lambda id: IStore(BugTask).get(BugTask, id))
+        orig_pre_iter_hook = pre_iter_hook
+
+        def pre_iter_hook(rows):
+            rows = load(BugTask, rows)
+            if orig_pre_iter_hook:
+                orig_pre_iter_hook(rows)
 
     if len(alternatives) == 1:
         [query, clauseTables, bugtask_decorator, join_tables,
-         has_duplicate_results, with_clause] = _build_query(
-             alternatives[0], use_flat)
+         has_duplicate_results, with_clause] = _build_query(alternatives[0])
         if with_clause:
             store = store.with_(with_clause)
         decorators.append(bugtask_decorator)
@@ -387,10 +269,10 @@
         if has_duplicate_results:
             origin = _build_origin(join_tables, clauseTables, start)
             outer_origin = _build_origin(orderby_joins, [], start)
-            want_inner = BugTaskFlat.bugtask_id if use_flat else BugTask.id
-            subquery = Select(want_inner, where=query, tables=origin)
+            subquery = Select(
+                BugTaskFlat.bugtask_id, where=query, tables=origin)
             result = store.using(*outer_origin).find(
-                want, In(want_inner, subquery))
+                want, In(BugTaskFlat.bugtask_id, subquery))
         else:
             origin = _build_origin(
                 join_tables + orderby_joins, clauseTables, start)
@@ -400,14 +282,12 @@
 
         for params in alternatives:
             [query, clauseTables, decorator, join_tables,
-             has_duplicate_results, with_clause] = _build_query(
-                 params, use_flat)
+             has_duplicate_results, with_clause] = _build_query(params)
             origin = _build_origin(join_tables, clauseTables, start)
             localstore = store
             if with_clause:
                 localstore = store.with_(with_clause)
-            want_inner = BugTaskFlat if use_flat else BugTask
-            next_result = localstore.using(*origin).find((want_inner,), query)
+            next_result = localstore.using(*origin).find(BugTaskFlat, query)
             results.append(next_result)
             # NB: assumes the decorators are all compatible.
             # This may need revisiting if e.g. searches on behalf of different
@@ -416,10 +296,7 @@
 
         resultset = reduce(lambda l, r: l.union(r), results)
         origin = _build_origin(
-            orderby_joins, [],
-            Alias(
-                resultset._get_select(),
-                "BugTaskFlat" if use_flat else "BugTask"))
+            orderby_joins, [], Alias(resultset._get_select(), "BugTaskFlat"))
         result = store.using(*origin).find(want)
 
     result.order_by(orderby_expression)
@@ -457,7 +334,7 @@
     return origin
 
 
-def _build_query(params, use_flat):
+def _build_query(params):
     """Build and return an SQL query with the given parameters.
 
     Also return the clauseTables and orderBy for the generated query.
@@ -466,16 +343,10 @@
         decorator to call on each returned row.
     """
     params = _require_params(params)
-    cols = flat_cols if use_flat else unflat_cols
 
-    if use_flat:
-        extra_clauses = []
-        clauseTables = []
-        join_tables = []
-    else:
-        extra_clauses = [Bug.id == BugTask.bugID]
-        clauseTables = [BugTask, Bug]
-        join_tables = []
+    extra_clauses = []
+    clauseTables = []
+    join_tables = []
 
     decorators = []
     has_duplicate_results = False
@@ -519,8 +390,8 @@
 
     # All the standard args filter on BugTaskFlat, except for
     # date_closed which isn't denormalised (yet?).
-    if params.date_closed is not None and use_flat:
-        join_tables.append(flat_bugtask_join)
+    if params.date_closed is not None:
+        join_tables.append(bugtask_join)
 
     if params.status is not None:
         extra_clauses.append(
@@ -607,8 +478,7 @@
     if params.attachmenttype is not None:
         if params.attachmenttype == BugAttachmentType.PATCH:
             extra_clauses.append(Bug.latest_patch_uploaded != None)
-            if use_flat:
-                join_tables.append(flat_bug_join)
+            join_tables.append(bug_join)
         else:
             extra_clauses.append(
                 cols['Bug.id'].is_in(
@@ -618,12 +488,10 @@
                             BugAttachment.type, params.attachmenttype))))
 
     if params.searchtext:
-        extra_clauses.append(_build_search_text_clause(
-            params, use_flat=use_flat))
+        extra_clauses.append(_build_search_text_clause(params))
 
     if params.fast_searchtext:
-        extra_clauses.append(_build_search_text_clause(
-            params, fast=True, use_flat=use_flat))
+        extra_clauses.append(_build_search_text_clause(params, fast=True))
 
     if params.subscriber is not None:
         clauseTables.append(BugSubscription)
@@ -855,7 +723,7 @@
                 Milestone.dateexpected <= dateexpected_before)
 
     clause, decorator = _get_bug_privacy_filter_with_decorator(
-        params.user, use_flat=use_flat)
+        params.user, use_flat=True)
     if clause:
         extra_clauses.append(SQL(clause))
         decorators.append(decorator)
@@ -913,7 +781,7 @@
         has_duplicate_results, with_clause)
 
 
-def _process_order_by(params, use_flat):
+def _process_order_by(params):
     """Process the orderby parameter supplied to search().
 
     This method ensures the sort order will be stable, and converting
@@ -942,34 +810,15 @@
     else:
         in_unique_context = False
 
-    if use_flat:
-        orderby_expression = flat_orderby_expression
-        unambiguous_cols = set([
-            BugTaskFlat.date_last_updated,
-            BugTaskFlat.datecreated,
-            BugTaskFlat.bugtask_id,
-            Bug.datecreated,
-            BugTask.date_assigned,
-            ])
-        if in_unique_context:
-            unambiguous_cols.add(BugTaskFlat.bug)
-    else:
-        orderby_expression = unflat_orderby_expression
-        # Bug.id and BugTask.bugID shouldn't really be here; they're
-        # ambiguous in a distribution or distroseries context. They're
-        # omitted from the new BugTaskFlat path, but kept in the legacy
-        # code in case it affects index selection.
-        unambiguous_cols = set([
-            Bug.date_last_updated,
-            Bug.datecreated,
-            Bug.id,
-            BugTask.bugID,
-            BugTask.date_assigned,
-            BugTask.datecreated,
-            BugTask.id,
-            ])
-        if in_unique_context:
-            unambiguous_cols.add(BugTask.bug)
+    unambiguous_cols = set([
+        BugTaskFlat.date_last_updated,
+        BugTaskFlat.datecreated,
+        BugTaskFlat.bugtask_id,
+        Bug.datecreated,
+        BugTask.date_assigned,
+        ])
+    if in_unique_context:
+        unambiguous_cols.add(BugTaskFlat.bug)
 
     # Translate orderby keys into corresponding Table.attribute
     # strings.
@@ -1006,16 +855,10 @@
         orderby_arg.append(order_clause)
 
     if ambiguous:
-        if use_flat:
-            if in_unique_context:
-                disambiguator = BugTaskFlat.bug_id
-            else:
-                disambiguator = BugTaskFlat.bugtask_id
+        if in_unique_context:
+            disambiguator = BugTaskFlat.bug_id
         else:
-            if in_unique_context:
-                disambiguator = BugTask.bugID
-            else:
-                disambiguator = BugTask.id
+            disambiguator = BugTaskFlat.bugtask_id
 
         if orderby_arg and not isinstance(orderby_arg[0], Desc):
             disambiguator = Desc(disambiguator)
@@ -1033,7 +876,7 @@
     return params
 
 
-def _build_search_text_clause(params, fast=False, use_flat=False):
+def _build_search_text_clause(params, fast=False):
     """Build the clause for searchtext."""
     if fast:
         assert params.searchtext is None, (
@@ -1044,14 +887,13 @@
             'Cannot use fast_searchtext at the same time as searchtext.')
         searchtext = params.searchtext
 
-    col = 'BugTaskFlat.fti' if use_flat else 'Bug.fti'
-
     if params.orderby is None:
         # Unordered search results aren't useful, so sort by relevance
         # instead.
-        params.orderby = [SQL("-rank(%s, ftq(?))" % col, params=(searchtext,))]
+        params.orderby = [
+            SQL("-rank(BugTaskFlat.fti, ftq(?))", params=(searchtext,))]
 
-    return SQL("%s @@ ftq(?)" % col, params=(searchtext,))
+    return SQL("BugTaskFlat.fti @@ ftq(?)", params=(searchtext,))
 
 
 def _build_status_clause(col, status):

=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
--- lib/lp/bugs/model/tests/test_bugtask.py	2012-04-18 22:26:33 +0000
+++ lib/lp/bugs/model/tests/test_bugtask.py	2012-05-01 07:46:21 +0000
@@ -1014,7 +1014,7 @@
         IPerson(person.account, None)
         # The should take 2 queries - one for the tasks, one for the related
         # products (eager loaded targets).
-        has_expected_queries = HasQueryCount(Equals(3))
+        has_expected_queries = HasQueryCount(Equals(4))
         # No extra queries should be issued to access a regular attribute
         # on the bug that would normally trigger lazy evaluation for security
         # checking.  Note that the 'id' attribute does not trigger a check.

=== modified file 'lib/lp/bugs/tests/test_bugsearch_conjoined.py'
--- lib/lp/bugs/tests/test_bugsearch_conjoined.py	2012-04-17 22:56:29 +0000
+++ lib/lp/bugs/tests/test_bugsearch_conjoined.py	2012-05-01 07:46:21 +0000
@@ -70,7 +70,7 @@
             list(self.bugtask_set.search(self.params))
         # 1 query for the tasks, 1 query for the product (target) eager
         # loading.
-        self.assertThat(recorder, HasQueryCount(Equals(3)))
+        self.assertThat(recorder, HasQueryCount(Equals(4)))
 
     def test_search_results_count_with_other_productseries_tasks(self):
         # Test with zero conjoined masters and bugtasks targeted to
@@ -166,7 +166,7 @@
             list(self.bugtask_set.search(self.params))
         # 1 query for the tasks, 1 query for the product (target) eager
         # loading.
-        self.assertThat(recorder, HasQueryCount(Equals(3)))
+        self.assertThat(recorder, HasQueryCount(Equals(4)))
 
     def test_search_results_count_with_other_productseries_tasks(self):
         # Test with zero conjoined masters and bugtasks targeted to
@@ -278,7 +278,7 @@
         Store.of(self.milestone).flush()
         with StormStatementRecorder() as recorder:
             list(self.bugtask_set.search(self.params))
-        self.assertThat(recorder, HasQueryCount(Equals(3)))
+        self.assertThat(recorder, HasQueryCount(Equals(4)))
 
     def test_search_results_count_with_other_productseries_tasks(self):
         # Test with zero conjoined masters and bugtasks targeted to

=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py	2012-04-27 06:29:59 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py	2012-05-01 07:46:21 +0000
@@ -33,7 +33,6 @@
 from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.sourcepackage import ISourcePackage
 from lp.services.database.sqlbase import convert_storm_clause_to_string
-from lp.services.features.testing import FeatureFixture
 from lp.services.searchbuilder import (
     all,
     any,
@@ -64,7 +63,7 @@
             params.setProduct(distribution)
         self.assertEqual(
             expected,
-            convert_storm_clause_to_string(_process_order_by(params, True)[0]))
+            convert_storm_clause_to_string(_process_order_by(params)[0]))
 
     def test_tiebreaker(self):
         # Requests for ambiguous sorts get a disambiguator of BugTask.id
@@ -1630,31 +1629,6 @@
         return [bugtask.bug.id for bugtask in expected_bugtasks]
 
 
-class UsingFlat:
-    """Use BugTaskFlat for searching."""
-
-    def setUp(self):
-        super(UsingFlat, self).setUp()
-        self.useFixture(
-            FeatureFixture({'bugs.bugtaskflat.search.enabled': 'on'}))
-
-
-class UsingLegacy:
-    """Use Bug and BugTask directly for searching."""
-
-    def test_private_bug_in_search_result_assignees(self):
-        # Private bugs are included in search results for the assignee.
-        with person_logged_in(self.owner):
-            self.bugtasks[-1].bug.setPrivate(True, self.owner)
-        bugtask = self.bugtasks[-1]
-        user = self.factory.makePerson()
-        admin = getUtility(IPersonSet).getByEmail('foo.bar@xxxxxxxxxxxxx')
-        with person_logged_in(admin):
-            bugtask.transitionToAssignee(user)
-        params = self.getBugTaskSearchParams(user=user)
-        self.assertSearchFinds(params, self.bugtasks)
-
-
 class TestMilestoneDueDateFiltering(TestCaseWithFactory):
 
     layer = LaunchpadFunctionalLayer
@@ -1689,33 +1663,27 @@
     loader = unittest.TestLoader()
     for bug_target_search_type_class in (
         PreloadBugtaskTargets, NoPreloadBugtaskTargets, QueryBugIDs):
-        for feature_mixin in (UsingLegacy, UsingFlat):
+        class_name = 'Test%s' % bug_target_search_type_class.__name__
+        class_bases = (
+            bug_target_search_type_class, ProductTarget, OnceTests,
+            SearchTestBase, TestCaseWithFactory)
+        test_class = type(class_name, class_bases, {})
+        suite.addTest(loader.loadTestsFromTestCase(test_class))
+
+        for target_mixin in bug_targets_mixins:
             class_name = 'Test%s%s' % (
                 bug_target_search_type_class.__name__,
-                feature_mixin.__name__)
-            mixins = [bug_target_search_type_class, feature_mixin]
+                target_mixin.__name__)
+            mixins = [
+                target_mixin, bug_target_search_type_class]
             class_bases = (
                 tuple(mixins)
-                + (ProductTarget, OnceTests, SearchTestBase,
-                   TestCaseWithFactory))
+                + (TargetTests, SearchTestBase, TestCaseWithFactory))
+            # Dynamically build a test class from the target mixin class,
+            # from the search type mixin class, from the mixin class
+            # having all tests and from a unit test base class.
             test_class = type(class_name, class_bases, {})
+            # Add the new unit test class to the suite.
             suite.addTest(loader.loadTestsFromTestCase(test_class))
-
-            for target_mixin in bug_targets_mixins:
-                class_name = 'Test%s%s%s' % (
-                    bug_target_search_type_class.__name__,
-                    target_mixin.__name__,
-                    feature_mixin.__name__)
-                mixins = [
-                    target_mixin, bug_target_search_type_class, feature_mixin]
-                class_bases = (
-                    tuple(mixins)
-                    + (TargetTests, SearchTestBase, TestCaseWithFactory))
-                # Dynamically build a test class from the target mixin class,
-                # from the search type mixin class, from the mixin class
-                # having all tests and from a unit test base class.
-                test_class = type(class_name, class_bases, {})
-                # Add the new unit test class to the suite.
-                suite.addTest(loader.loadTestsFromTestCase(test_class))
     suite.addTest(loader.loadTestsFromName(__name__))
     return suite

=== modified file 'lib/lp/registry/browser/tests/test_milestone.py'
--- lib/lp/registry/browser/tests/test_milestone.py	2012-04-17 22:56:29 +0000
+++ lib/lp/registry/browser/tests/test_milestone.py	2012-05-01 07:46:21 +0000
@@ -282,18 +282,19 @@
 
     def test_bugtasks_queries(self):
         # The view.bugtasks attribute will make several queries:
-        #  1. Load bugtasks
-        #  2. Load bugs.
-        #  3. Loads the target (sourcepackagename / product)
-        #  4. Load assignees (Person, Account, and EmailAddress).
-        #  5. Load links to specifications.
-        #  6. Load links to branches.
-        #  7. Loads milestones
-        #  8. Loads tags
-        #  9. All related people
+        #  1. Search for bugtask IDs.
+        #  2. Load bugtasks
+        #  3. Load bugs.
+        #  4. Loads the target (sourcepackagename / product)
+        #  5. Load assignees (Person, Account, and EmailAddress).
+        #  6. Load links to specifications.
+        #  7. Load links to branches.
+        #  8. Loads milestones
+        #  9. Loads tags
+        #  10. All related people
         bugtask_count = 10
         self.assert_bugtasks_query_count(
-            self.milestone, bugtask_count, query_limit=10)
+            self.milestone, bugtask_count, query_limit=11)
 
     def test_milestone_eager_loading(self):
         # Verify that the number of queries does not increase with more
@@ -408,17 +409,18 @@
     def test_bugtasks_queries(self):
         # The view.bugtasks attribute will make several queries:
         #  1. For each project in the group load all the dev focus series ids.
-        #  2. Load bugtasks.
-        #  3. Load bugs.
-        #  4. Load assignees (Person, Account, and EmailAddress).
-        #  5. Load links to specifications.
-        #  6. Load links to branches.
-        #  7. Loads milestones.
-        #  8. Loads tags.
-        #  9. All related people.
+        #  2. Search for bugtask IDs.
+        #  3. Load bugtasks.
+        #  4. Load bugs.
+        #  5. Load assignees (Person, Account, and EmailAddress).
+        #  6. Load links to specifications.
+        #  7. Load links to branches.
+        #  8. Loads milestones.
+        #  9. Loads tags.
+        #  10. All related people.
         bugtask_count = 10
         self.assert_bugtasks_query_count(
-            self.milestone, bugtask_count, query_limit=10)
+            self.milestone, bugtask_count, query_limit=11)
 
     def test_milestone_eager_loading(self):
         # Verify that the number of queries does not increase with more

=== modified file 'lib/lp/registry/browser/tests/test_person.py'
--- lib/lp/registry/browser/tests/test_person.py	2012-04-27 16:42:34 +0000
+++ lib/lp/registry/browser/tests/test_person.py	2012-05-01 07:46:21 +0000
@@ -1321,7 +1321,7 @@
         Store.of(milestones[0]).invalidate()
         with StormStatementRecorder() as recorder:
             self.assertEqual(expected, view.getMilestoneWidgetValues())
-        self.assertThat(recorder, HasQueryCount(LessThan(5)))
+        self.assertThat(recorder, HasQueryCount(LessThan(6)))
 
     def test_context_description(self):
         # view.context_description returns a string that can be used

=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py	2012-04-20 05:00:49 +0000
+++ lib/lp/registry/tests/test_person.py	2012-05-01 07:46:21 +0000
@@ -1450,4 +1450,4 @@
         # 9. One to get all sourcepackagenames;
         # 10. One to get all distroseries of a bug's distro. (See comment on
         # getAssignedBugTasksDueBefore() to understand why it's needed)
-        self.assertThat(recorder, HasQueryCount(Equals(11)))
+        self.assertThat(recorder, HasQueryCount(Equals(12)))

=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py	2012-04-23 14:12:48 +0000
+++ lib/lp/services/features/flags.py	2012-05-01 07:46:21 +0000
@@ -63,12 +63,6 @@
      '',
      '',
      'https://bugs.launchpad.net/launchpad/+bug/678090'),
-    ('bugs.bugtaskflat.search.enabled',
-     'boolean',
-     'Use BugTaskFlat for bug searches.',
-     '',
-     '',
-     ''),
     ('bugs.bugtracker_components.enabled',
      'boolean',
      ('Enables the display of bugtracker components.'),


Follow ups