← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/dsp-historic-attributes into lp:launchpad

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/dsp-historic-attributes into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~sinzui/launchpad/dsp-historic-attributes/+merge/78106

Update the DSP vocab to support official packages and historic values.

    Launchpad bug: https://bugs.launchpad.net/bugs/826516
    Pre-implementation: no one

The DSP vocab was intended to solve a number of timeout and usability bugs
in target pickers, but it was flawed. Firstly it could take 20 minutes to
get return the results where we expect the query to be subsecond. The
vocabulary used suoyuz publishing data, which meant that it did not support
official source package branches. The results were not ranked either, so
there was no guarantee that the exact match appeared on the first page of
results.

The DSPCache table used for archive searches contained much of the
information that was discussed as data needed for the DSP to make it
fast for searching official packages. A spike in SQL showed that a fast query
could be constructed to support official packages that only have branches
and the results could be ranked.

There were also concerns that the DSP vocabulary did not support historic
DSP values, those that were once valid, but users should not be able to
select them when reporting new bugs or asking questions.

This feature is be disclosure.dsp_picker.enabled flag and will only be
visible on qastaging. The UI needs love and the vocab itself needs to
prove it works with production data. Once we are certain of the data
that the DSP table must provide, we can fix the schema (bug 851266)
This branch implcitly fixes two other bugs about search suggests invalid
packages or does not match binary packages.

--------------------------------------------------------------------

RULES

    * Update the DSP vocab to permit the current value for a DSP so that
      pages with deleted packages will continue to work.
      * Used the AnswersDistributionVocab as a guide where the DSP used
        to initialise the vocab is always included in the vocab in the
        __contain__() and term rules existing values can be rendered in
        pages.
    * Replace the DSP search query with the spike query.
      * Limit the results to 60 (10 batches that the user might be willing
        to view)
      * Rank only the results that will be returned.
        * Exact match on source package name is first
        * Followed by exact match on binary package name
        * Then partial matches on the source package name
        * And lastly partial matches on the binary package name
      * Ensure that official DSPs that only have branches can be searched.
      * Ensure that only main and partner archives are searched.


QA

    To verify that the vocab, enable the feature:
    disclosure.dsp_picker.enabled	default	0	on

    * Visit https://bugs.qastaging.launchpad.net/charm/+source/apache2/+filebug
    * Verify  you cannot report a bug because apache2 does not exist.
    * Visit https://bugs.qastaging.launchpad.net/charm/+source/jenkins/+filebug
    * Verify you can report a bug.
    * Expand the task row to reveal the target widget.
    * Search for 'jenkins' using the package field's choose link.
    * Verify 'jenkins' is the first item in the listing
    * Search for 'slave'
    * Verify that 'jenkins-slave' is found
    * Search for 'ganglia-webfrontend'
    * Verify that is the first item in the listing.
    * Search for 'webfrontend'
    * Verify that is the listing.
    * Search for 'apache'
    * Verify there are no results
    * Visit https://bugs.qastaging.launchpad.net/ubuntu/+source/devicekit/+bug/695362
    * Verify the page loads.
    * Expand the bugtask
    * Enter any text into the comment field.
    * Save changes
    * Verify the page reloads without error
    * Verify the comment was added.

    Looking at a dev instance also requires the feature flag to be enabled.
    Search requires an update to the sample db because the data is incomplete.
        Insert into distributionsourcepackage(sourcepackagename, distribution)
        select sourcepackagename, distribution
        from distributionsourcepackagecache;


LINT

    lib/lp/registry/configure.zcml
    lib/lp/registry/interfaces/distributionsourcepackage.py
    lib/lp/registry/model/distributionsourcepackage.py
    lib/lp/registry/tests/test_distributionsourcepackage.py
    lib/lp/registry/tests/test_dsp_vocabularies.py
    lib/lp/registry/vocabularies.py


TEST

    ./bin/test -vvc -t TestDistributionSourcePackage.test \
        lp.registry.tests.test_distributionsourcepackage
    ./bin/test -vvc lp.registry.tests.test_dsp_vocabularies


IMPLEMENTATION

Added is_official so that callsites do not need to access the private
property. This property will probably change when we choose to exclude
delete packages from unsupported series.
    lib/lp/registry/configure.zcml
    lib/lp/registry/interfaces/distributionsourcepackage.py
    lib/lp/registry/model/distributionsourcepackage.py

Add a rule to record the DSP used as the context of the field and to
ensure it is always in the vocabulary. This ensures that historic packages
that have been deleted continue to work This also prevents errors when
users view bugs and questions targeted to packages that are now considered
invalid, but were not a few years ago.

Rework the search query based on a spike that uses the DSPCache table that
contains the words that the old query struggled to locate. The query supports
package branches unlike its predecessor and it is much faster. The UI is a
mess and I expect to make more changes to the vocab to fix the UI.
    lib/lp/registry/tests/test_distributionsourcepackage.py
    lib/lp/registry/tests/test_dsp_vocabularies.py
    lib/lp/registry/vocabularies.py
-- 
https://code.launchpad.net/~sinzui/launchpad/dsp-historic-attributes/+merge/78106
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/dsp-historic-attributes into lp:launchpad.
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2011-09-28 03:21:49 +0000
+++ lib/lp/registry/configure.zcml	2011-10-04 14:57:06 +0000
@@ -488,6 +488,7 @@
                 getUsedBugTagsWithOpenCounts
                 getVersion
                 get_distroseries_packages
+                is_official
                 latest_overall_publication
                 name
                 official_bug_tags

=== modified file 'lib/lp/registry/interfaces/distributionsourcepackage.py'
--- lib/lp/registry/interfaces/distributionsourcepackage.py	2011-09-19 22:41:01 +0000
+++ lib/lp/registry/interfaces/distributionsourcepackage.py	2011-10-04 14:57:06 +0000
@@ -82,6 +82,9 @@
             # interfaces/product.py.
             schema=Interface))
 
+    is_official = Attribute(
+        'Is this source package officially in the distribution?')
+
     summary = Attribute(
         'The summary of binary packages built from this package')
 

=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py	2011-09-28 03:28:50 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py	2011-10-04 14:57:06 +0000
@@ -207,6 +207,14 @@
         # in the database.
         return self._get(self.distribution, self.sourcepackagename)
 
+    @property
+    def is_official(self):
+        """See `DistributionSourcePackage`."""
+        # This will need to verify that the package has not been deleted
+        # in the future.
+        return self._get(
+            self.distribution, self.sourcepackagename) is not None
+
     def delete(self):
         """See `DistributionSourcePackage`."""
         dsp_in_db = self._self_in_database

=== modified file 'lib/lp/registry/tests/test_distributionsourcepackage.py'
--- lib/lp/registry/tests/test_distributionsourcepackage.py	2011-09-20 16:40:13 +0000
+++ lib/lp/registry/tests/test_distributionsourcepackage.py	2011-10-04 14:57:06 +0000
@@ -115,6 +115,16 @@
         dsp = sp.distribution_sourcepackage
         self.assertTrue(dsp.delete())
 
+    def test_is_official_with_db_true(self):
+        # A DSP is official when it is represented in the database.
+        dsp = self.factory.makeDistributionSourcePackage(with_db=True)
+        self.assertTrue(dsp.is_official)
+
+    def test_is_official_without_db_false(self):
+        # A DSP is not official if it is virtual.
+        dsp = self.factory.makeDistributionSourcePackage(with_db=False)
+        self.assertFalse(dsp.is_official)
+
 
 class TestDistributionSourcePackageFindRelatedArchives(TestCaseWithFactory):
 

=== modified file 'lib/lp/registry/tests/test_dsp_vocabularies.py'
--- lib/lp/registry/tests/test_dsp_vocabularies.py	2011-08-11 13:01:47 +0000
+++ lib/lp/registry/tests/test_dsp_vocabularies.py	2011-10-04 14:57:06 +0000
@@ -5,9 +5,20 @@
 
 __metaclass__ = type
 
+import transaction
+
+from zope.component import getUtility
+
 from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
-from canonical.testing.layers import DatabaseFunctionalLayer
+from canonical.testing.layers import (
+    DatabaseFunctionalLayer,
+    reconnect_stores,
+    )
+from lp.registry.interfaces.distribution import IDistributionSet
 from lp.registry.vocabularies import DistributionSourcePackageVocabulary
+from lp.soyuz.model.distributionsourcepackagecache import (
+    DistributionSourcePackageCache,
+    )
 from lp.testing import TestCaseWithFactory
 
 
@@ -28,6 +39,7 @@
         vocabulary = DistributionSourcePackageVocabulary(dsp)
         self.assertEqual(dsp, vocabulary.context)
         self.assertEqual(dsp.distribution, vocabulary.distribution)
+        self.assertEqual(dsp, vocabulary.dsp)
 
     def test_init_dsp_bugtask(self):
         # A dsp bugtask can be the context
@@ -37,6 +49,7 @@
         vocabulary = DistributionSourcePackageVocabulary(bugtask)
         self.assertEqual(bugtask, vocabulary.context)
         self.assertEqual(dsp.distribution, vocabulary.distribution)
+        self.assertEqual(dsp, vocabulary.dsp)
 
     def test_init_dsp_question(self):
         # A dsp bugtask can be the context
@@ -47,6 +60,7 @@
         vocabulary = DistributionSourcePackageVocabulary(question)
         self.assertEqual(question, vocabulary.context)
         self.assertEqual(dsp.distribution, vocabulary.distribution)
+        self.assertEqual(dsp, vocabulary.dsp)
 
     def test_init_no_distribution(self):
         # The distribution is None if the context cannot be adapted to a
@@ -55,6 +69,7 @@
         vocabulary = DistributionSourcePackageVocabulary(project)
         self.assertEqual(project, vocabulary.context)
         self.assertEqual(None, vocabulary.distribution)
+        self.assertEqual(None, vocabulary.dsp)
 
     def test_getDistributionAndPackageName_distro_and_package(self):
         # getDistributionAndPackageName() returns a tuple of distribution
@@ -87,23 +102,29 @@
         self.assertEqual(default_distro, distribution)
         self.assertEqual('pting', package_name)
 
-    def test_contains_true(self):
-        # The vocabulary contains DSPs that have SPPH in the distro.
-        spph = self.factory.makeSourcePackagePublishingHistory()
-        dsp = spph.sourcepackagerelease.distrosourcepackage
-        vocabulary = DistributionSourcePackageVocabulary(dsp)
-        self.assertTrue(dsp in vocabulary)
-
-    def test_contains_false(self):
-        # The vocabulary does not contain DSPs without SPPH.
-        spn = self.factory.makeSourcePackageName(name='foo')
-        dsp = self.factory.makeDistributionSourcePackage(
-            sourcepackagename=spn)
-        vocabulary = DistributionSourcePackageVocabulary(dsp)
+    def test_contains_true_without_init(self):
+        # The vocabulary contains official DSPs.
+        dsp = self.factory.makeDistributionSourcePackage(with_db=True)
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        self.assertTrue(dsp in vocabulary)
+
+    def test_contains_true_with_init(self):
+        # The vocabulary does contain the DSP passed to init when
+        # it is not official.
+        dsp = self.factory.makeDistributionSourcePackage(with_db=False)
+        vocabulary = DistributionSourcePackageVocabulary(dsp)
+        self.assertTrue(dsp in vocabulary)
+
+    def test_contains_false_without_init(self):
+        # The vocabulary does not contain DSPs that are not official
+        # that were not passed to init.
+        dsp = self.factory.makeDistributionSourcePackage(with_db=False)
+        vocabulary = DistributionSourcePackageVocabulary(None)
         self.assertFalse(dsp in vocabulary)
 
     def test_toTerm_raises_error(self):
-        # An error is raised for DSP/SPNs without publishing history.
+        # An error is raised for DSP/SPNs that are not official and are
+        # not in the vocabulary.
         dsp = self.factory.makeDistributionSourcePackage(
             sourcepackagename='foo')
         vocabulary = DistributionSourcePackageVocabulary(dsp.distribution)
@@ -145,7 +166,7 @@
         self.assertEqual(dsp, term.value)
 
     def test_getTermByToken_error(self):
-        # An error is raised if the token does not match a published DSP.
+        # An error is raised if the token does not match a official DSP.
         dsp = self.factory.makeDistributionSourcePackage(
             sourcepackagename='foo')
         vocabulary = DistributionSourcePackageVocabulary(dsp.distribution)
@@ -153,7 +174,7 @@
         self.assertRaises(LookupError, vocabulary.getTermByToken, token)
 
     def test_getTermByToken_token(self):
-        # The term is return if it matches a published DSP.
+        # The term is return if it matches an official DSP.
         spph = self.factory.makeSourcePackagePublishingHistory()
         dsp = spph.sourcepackagerelease.distrosourcepackage
         vocabulary = DistributionSourcePackageVocabulary(dsp.distribution)
@@ -170,6 +191,31 @@
         results = vocabulary.searchForTerms(dsp.name)
         self.assertIs(0, results.count())
 
+    def makeDSPCache(self, distro_name, package_name, make_distro=True,
+                     official=True, binary_names=None, archive=None):
+        if make_distro:
+            distribution = self.factory.makeDistribution(name=distro_name)
+        else:
+            distribution = getUtility(IDistributionSet).getByName(distro_name)
+        dsp = self.factory.makeDistributionSourcePackage(
+            distribution=distribution, sourcepackagename=package_name,
+            with_db=official)
+        if archive is None:
+            archive = dsp.distribution.main_archive
+        else:
+            archive = self.factory.makeArchive(
+                distribution=distribution, purpose=archive)
+        transaction.commit()
+        reconnect_stores('statistician')
+        DistributionSourcePackageCache(
+            distribution=dsp.distribution,
+            sourcepackagename=dsp.sourcepackagename,
+            archive=archive,
+            name=package_name,
+            binpkgnames=binary_names)
+        transaction.commit()
+        reconnect_stores('launchpad')
+
     def test_searchForTerms_None(self):
         # Searching for nothing gets you that.
         vocabulary = DistributionSourcePackageVocabulary(
@@ -177,68 +223,121 @@
         results = vocabulary.searchForTerms()
         self.assertIs(0, results.count())
 
-    def assertTermsEqual(self, expected, actual):
-        # Assert two given terms are equal.
-        self.assertEqual(expected.token, actual.token)
-        self.assertEqual(expected.title, actual.title)
-        self.assertEqual(expected.value, actual.value)
-
-    def test_searchForTerms_published_source(self):
-        # When we search for a source package name that is published, it is
-        # returned.
-        spph = self.factory.makeSourcePackagePublishingHistory()
-        vocabulary = DistributionSourcePackageVocabulary(
-            context=spph.distroseries.distribution)
-        results = vocabulary.searchForTerms(query=spph.source_package_name)
-        self.assertTermsEqual(
-            vocabulary.toTerm(spph.source_package_name), list(results)[0])
-
-    def test_searchForTerms_unpublished_source(self):
-        # If the source package name isn't published in the distribution,
-        # we get no results.
-        spph = self.factory.makeSourcePackagePublishingHistory()
-        vocabulary = DistributionSourcePackageVocabulary(
-            context=self.factory.makeDistribution())
-        results = vocabulary.searchForTerms(query=spph.source_package_name)
-        self.assertEqual([], list(results))
-
-    def test_searchForTerms_unpublished_binary(self):
-        # If the binary package name isn't published in the distribution,
-        # we get no results.
-        bpph = self.factory.makeBinaryPackagePublishingHistory()
-        vocabulary = DistributionSourcePackageVocabulary(
-            context=self.factory.makeDistribution())
-        results = vocabulary.searchForTerms(query=bpph.binary_package_name)
-        self.assertEqual([], list(results))
-
-    def test_searchForTerms_published_binary(self):
-        # We can search for a binary package name, which returns the
-        # relevant SPN.
-        bpph = self.factory.makeBinaryPackagePublishingHistory()
-        distribution = bpph.distroarchseries.distroseries.distribution
-        vocabulary = DistributionSourcePackageVocabulary(
-            context=distribution)
-        spn = bpph.binarypackagerelease.build.source_package_release.name
-        results = vocabulary.searchForTerms(query=bpph.binary_package_name)
-        self.assertTermsEqual(vocabulary.toTerm(spn), list(results)[0])
-
-    def test_searchForTerms_published_multiple_binaries(self):
-        # Searching for a subset of a binary package name returns the SPN
-        # that built the binary package.
-        spn = self.factory.getOrMakeSourcePackageName('xorg')
-        spr = self.factory.makeSourcePackageRelease(sourcepackagename=spn)
-        das = self.factory.makeDistroArchSeries()
-        self.factory.makeSourcePackagePublishingHistory(
-            sourcepackagerelease=spr, distroseries=das.distroseries)
-        for name in ('xorg-common', 'xorg-server', 'xorg-video-intel'):
-            bpn = self.factory.getOrMakeBinaryPackageName(name)
-            bpb = self.factory.makeBinaryPackageBuild(
-                source_package_release=spr, distroarchseries=das)
-            bpr = self.factory.makeBinaryPackageRelease(
-                binarypackagename=bpn, build=bpb)
-            self.factory.makeBinaryPackagePublishingHistory(
-                binarypackagerelease=bpr, distroarchseries=das)
-        vocabulary = DistributionSourcePackageVocabulary(
-            context=das.distroseries.distribution)
-        results = vocabulary.searchForTerms(query='xorg-se')
-        self.assertTermsEqual(vocabulary.toTerm(spn), list(results)[0])
+    def test_searchForTerms_exact_offcial_source_name(self):
+        # Exact source name matches are found.
+        self.makeDSPCache('fnord', 'snarf')
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/snarf')
+        terms = list(results)
+        self.assertEqual(1, len(terms))
+        self.assertEqual('fnord/snarf', terms[0].token)
+
+    def test_searchForTerms_similar_offcial_source_name(self):
+        # Partial source name matches are found.
+        self.makeDSPCache('fnord', 'pting-snarf-ack')
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/snarf')
+        terms = list(results)
+        self.assertEqual(1, len(terms))
+        self.assertEqual('fnord/pting-snarf-ack', terms[0].token)
+
+    def test_searchForTerms_exact_binary_name(self):
+        # Exact binary name matches are found.
+        self.makeDSPCache(
+            'fnord', 'snarf', binary_names='pting-dev pting ack')
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/pting')
+        terms = list(results)
+        self.assertEqual(1, len(terms))
+        self.assertEqual('fnord/snarf', terms[0].token)
+
+    def test_searchForTerms_similar_binary_name(self):
+        # Partial binary name matches are found.
+        self.makeDSPCache(
+            'fnord', 'snarf', binary_names='thrpp pting-dev ack')
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/pting')
+        terms = list(results)
+        self.assertEqual(1, len(terms))
+        self.assertEqual('fnord/snarf', terms[0].token)
+
+    def test_searchForTerms_exact_unofficial_source_name(self):
+        # Unofficial source packages are not found by search.
+        self.makeDSPCache('fnord', 'snarf', official=False)
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/snarf')
+        terms = list(results)
+        self.assertEqual(0, len(terms))
+
+    def test_searchForTerms_similar_unofficial_binary_name(self):
+        # Unofficial binary packages are not found by search.
+        self.makeDSPCache(
+            'fnord', 'snarf', official=False, binary_names='thrpp pting ack')
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/pting')
+        terms = list(results)
+        self.assertEqual(0, len(terms))
+
+    def test_searchForTerms_match_official_source_package_branch(self):
+        # The official package that is only a branch can be matched
+        # by source name if it was built in another distro.
+        self.makeDSPCache('fnord', 'snarf')
+        distribution = self.factory.makeDistribution(name='pting')
+        self.factory.makeDistributionSourcePackage(
+            distribution=distribution, sourcepackagename='snarf',
+            with_db=True)
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='pting/snarf')
+        terms = list(results)
+        self.assertEqual(1, len(terms))
+        self.assertEqual('pting/snarf', terms[0].token)
+
+    def test_searchForTerms_match_official_binary_package_branch(self):
+        # The official package that is only a branch can be matched
+        # by binary name if it was built in another distro.
+        self.makeDSPCache(
+            'fnord', 'snarf', binary_names='thrpp snarf-dev ack')
+        distribution = self.factory.makeDistribution(name='pting')
+        self.factory.makeDistributionSourcePackage(
+            distribution=distribution, sourcepackagename='snarf',
+            with_db=True)
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='pting/ack')
+        terms = list(results)
+        self.assertEqual(1, len(terms))
+        self.assertEqual('pting/snarf', terms[0].token)
+
+    def test_searchForTerms_ranking(self):
+        # Exact matches are ranked higher than similar matches.
+        self.makeDSPCache('fnord', 'snarf')
+        self.makeDSPCache('fnord', 'snarf-server', make_distro=False)
+        self.makeDSPCache(
+            'fnord', 'pting-devel', binary_names='snarf', make_distro=False)
+        self.makeDSPCache(
+            'fnord', 'pting-client', binary_names='snarf-common',
+            make_distro=False)
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/snarf')
+        terms = list(results)
+        self.assertEqual(4, len(terms))
+        self.assertEqual('fnord/snarf', terms[0].token)
+        self.assertEqual('fnord/pting-devel', terms[1].token)
+        self.assertEqual('fnord/snarf-server', terms[2].token)
+        self.assertEqual('fnord/pting-client', terms[3].token)
+
+    def test_searchForTerms_partner_archive(self):
+        # Packages in partner archives are searched.
+        self.makeDSPCache('fnord', 'snarf', archive='partner')
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/snarf')
+        terms = list(results)
+        self.assertEqual(1, len(terms))
+        self.assertEqual('fnord/snarf', terms[0].token)
+
+    def test_searchForTerms_ppa_archive(self):
+        # Packages in PPAs are ignored.
+        self.makeDSPCache('fnord', 'snarf', archive='ppa')
+        vocabulary = DistributionSourcePackageVocabulary(None)
+        results = vocabulary.searchForTerms(query='fnord/snarf')
+        terms = list(results)
+        self.assertEqual(0, len(terms))

=== modified file 'lib/lp/registry/vocabularies.py'
--- lib/lp/registry/vocabularies.py	2011-09-16 17:17:43 +0000
+++ lib/lp/registry/vocabularies.py	2011-10-04 14:57:06 +0000
@@ -202,13 +202,8 @@
     cachedproperty,
     get_property_cache,
     )
-from lp.soyuz.enums import PackagePublishingStatus
-from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
-from lp.soyuz.model.binarypackagename import BinaryPackageName
-from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
+from lp.soyuz.enums import ArchivePurpose
 from lp.soyuz.model.distroarchseries import DistroArchSeries
-from lp.soyuz.model.publishing import SourcePackagePublishingHistory
-from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
 
 
 class BasePersonVocabulary:
@@ -2191,6 +2186,7 @@
     implements(IHugeVocabulary)
     displayname = 'Select a package'
     step_title = 'Search by name or distro/name'
+    LIMIT = 60
 
     def __init__(self, context):
         self.context = context
@@ -2206,8 +2202,16 @@
             self.distribution = IDistribution(target)
         except TypeError:
             self.distribution = None
+        if IDistributionSourcePackage.providedBy(target):
+            self.dsp = target
+        else:
+            self.dsp = None
 
     def __contains__(self, spn_or_dsp):
+        if spn_or_dsp == self.dsp:
+            # Historic values are always valid. The DSP used to
+            # initialize the vocabulary is always included.
+            return True
         try:
             self.toTerm(spn_or_dsp)
             return True
@@ -2249,18 +2253,11 @@
             distribution = distribution or self.distribution
             if distribution is not None and spn_or_dsp is not None:
                 dsp = distribution.getSourcePackage(spn_or_dsp)
-        try:
+        if dsp is not None and (dsp == self.dsp or dsp.is_official):
             token = '%s/%s' % (dsp.distribution.name, dsp.name)
             summary = '%s (%s)' % (token, dsp.name)
-
-            # We try to get the binaries for the dsp; if this fails, we return
-            # lookup error instead.
-            dsp.publishing_history[0].getBuiltBinaries()
             return SimpleTerm(dsp, token, summary)
-            #return SimpleTerm(dsp, token, summary)
-        except (IndexError, AttributeError):
-            # Either the DSP was None or there is no publishing history.
-            raise LookupError(distribution, spn_or_dsp)
+        raise LookupError(distribution, spn_or_dsp)
 
     def getTerm(self, spn_or_dsp):
         """See `IBaseVocabulary`."""
@@ -2282,44 +2279,45 @@
             # widget must encourage the <distro>/<package> search format.
             return EmptyResultSet()
         search_term = unicode(query)
-        store = IStore(SourcePackagePublishingHistory)
-        dsps = store.using(
-            SourcePackagePublishingHistory,
-            LeftJoin(
-                SourcePackageRelease,
-                SourcePackagePublishingHistory.sourcepackagereleaseID ==
-                    SourcePackageRelease.id),
-            LeftJoin(
-                SourcePackageName,
-                SourcePackageRelease.sourcepackagenameID ==
-                    SourcePackageName.id),
-            LeftJoin(
-                DistributionSourcePackageInDatabase,
-                SourcePackageName.id ==
-                    DistributionSourcePackageInDatabase.sourcepackagename_id),
-            LeftJoin(
-                BinaryPackageBuild,
-                BinaryPackageBuild.source_package_release_id ==
-                    SourcePackageRelease.id),
-            LeftJoin(
-                BinaryPackageRelease,
-                BinaryPackageRelease.buildID == BinaryPackageBuild.id),
-            LeftJoin(
-                BinaryPackageName,
-                BinaryPackageRelease.binarypackagenameID ==
-                    BinaryPackageName.id
-            )).find(
-                DistributionSourcePackageInDatabase,
-                DistributionSourcePackageInDatabase.distribution_id ==
-                    distribution.id,
-                SourcePackagePublishingHistory.status.is_in((
-                    PackagePublishingStatus.PENDING,
-                    PackagePublishingStatus.PUBLISHED)),
-                SourcePackagePublishingHistory.archive ==
-                    distribution.main_archive,
-                Or(
-                    SourcePackageName.name.contains_string(search_term),
-                    BinaryPackageName.name.contains_string(
-                        search_term))).config(distinct=True)
-        # XXX sinzui 2011-07-26: This query ignored SPN branches.
+        store = IStore(DistributionSourcePackageInDatabase)
+        # Construct the searchable text that could live in the DSP table.
+        # Limit the results to ensure the user could see all the batches.
+        # Rank only what is returned: exact source name, exact binary
+        # name, partial source name, and lastly partial binary name.
+        searchable_dsp = SQL("""
+            SELECT dsp.id, dsps.name, dsps.binpkgnames, rank
+            FROM DistributionSourcePackage dsp
+                JOIN (
+                SELECT DISTINCT ON (dspc.sourcepackagename)
+                    dspc.sourcepackagename, dspc.name, dspc.binpkgnames,
+                    CASE WHEN dspc.name = ? THEN 100
+                        WHEN dspc.binpkgnames SIMILAR TO
+                            '(^| )' || ? || '( |$)' THEN 75
+                        WHEN dspc.name SIMILAR TO
+                            '(^|.*-)' || ? || '(-|$)' THEN 50
+                        WHEN dspc.binpkgnames SIMILAR TO
+                            '(^|.*-)' || ? || '(-| |$)' THEN 25
+                        ELSE 1
+                        END AS rank
+                FROM DistributionSourcePackageCache dspc
+                    JOIN Archive a ON dspc.archive = a.id AND a.purpose IN (
+                        ?, ?)
+                WHERE
+                    dspc.name like '%%' || ? || '%%'
+                    OR dspc.binpkgnames like '%%' || ? || '%%'
+                LIMIT ?
+                ) dsps ON dsp.sourcepackagename = dsps.sourcepackagename
+            WHERE
+                dsp.distribution = ?
+            ORDER BY rank DESC
+            """, (search_term, search_term, search_term, search_term,
+                  ArchivePurpose.PRIMARY.value, ArchivePurpose.PARTNER.value,
+                  search_term, search_term, self.LIMIT, distribution.id))
+        matching_with = With('SearchableDSP', searchable_dsp)
+        # It might be possible to return the source name and binary names to
+        # reduce the work of the picker adapter.
+        dsps = store.with_(matching_with).using(
+            SQL('SearchableDSP'), DistributionSourcePackageInDatabase).find(
+            DistributionSourcePackageInDatabase,
+            SQL('DistributionSourcePackage.id = SearchableDSP.id'))
         return CountableIterator(dsps.count(), dsps, self.toTerm)


Follow ups