← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:move-publishing-queries into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:move-publishing-queries into launchpad:master.

Commit message:
Move publishing queries from DistroSeries to PublishingSet

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/417751

The existence of `DistroSeries.getSourcePackagePublishing` and `DistroSeries.getBinaryPackagePublishing` was a historical anomaly, as this sort of publishing-specific code should live in `lp.soyuz` or `lp.archivepublisher`.  Move these to the more appropriate `PublishingSet.getSourcesForPublishing` and `PublishingSet.getBinariesForPublishing`.

As well as cleaning up some minor technical debt, this helps to prepare for Artifactory publishing, where we're likely to need to be able to query for publications in an archive without filtering by series.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:move-publishing-queries into launchpad:master.
diff --git a/lib/lp/archivepublisher/publishing.py b/lib/lp/archivepublisher/publishing.py
index a5a0cc4..e280b79 100644
--- a/lib/lp/archivepublisher/publishing.py
+++ b/lib/lp/archivepublisher/publishing.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
@@ -910,8 +910,9 @@ class Publisher:
             get_sources_path(self._config, suite_name, component),
             self._config.temproot, distroseries.index_compressors)
 
-        for spp in distroseries.getSourcePackagePublishing(
-                pocket, component, self.archive):
+        for spp in getUtility(IPublishingSet).getSourcesForPublishing(
+                archive=self.archive, distroseries=distroseries, pocket=pocket,
+                component=component):
             stanza = build_source_stanza_fields(
                 spp.sourcepackagerelease, spp.component, spp.section)
             source_index.write(stanza.makeOutput().encode('utf-8') + b'\n\n')
@@ -937,8 +938,9 @@ class Publisher:
                         self._config, suite_name, component, arch, subcomp),
                     self._config.temproot, distroseries.index_compressors)
 
-            for bpp in distroseries.getBinaryPackagePublishing(
-                    arch.architecturetag, pocket, component, self.archive):
+            for bpp in getUtility(IPublishingSet).getBinariesForPublishing(
+                    archive=self.archive, distroarchseries=arch, pocket=pocket,
+                    component=component):
                 subcomp = FORMAT_TO_SUBCOMPONENT.get(
                     bpp.binarypackagerelease.binpackageformat)
                 if subcomp not in indices:
diff --git a/lib/lp/registry/doc/distroseries.txt b/lib/lp/registry/doc/distroseries.txt
index 7d202f5..82a8ff8 100644
--- a/lib/lp/registry/doc/distroseries.txt
+++ b/lib/lp/registry/doc/distroseries.txt
@@ -232,7 +232,6 @@ other things) the uploader for validating incoming uploads.
     devel
     translations
 
-    >>> from lp.soyuz.interfaces.component import IComponentSet
     >>> from lp.soyuz.interfaces.section import ISectionSet
     >>> python = getUtility(ISectionSet).ensure('python')
 
@@ -490,50 +489,9 @@ upstream.
 SourcePackagePublishingHistory
 ------------------------------
 
-IDistroSeries.getSourcePackagePublishing returns all the ISPPH
-records for a given status in a given pocket. It makes easy to
-generate a list of currently published sources for override-check, for
-instance. it can also be used to generate the archive packages list in
-the future.
-
-    >>> ubuntu = getUtility(IDistributionSet)['ubuntu']
-    >>> hoary = ubuntu['hoary']
-    >>> component_main = getUtility(IComponentSet)['main']
-    >>> component_multiverse = getUtility(IComponentSet)['multiverse']
-    >>> debian_archive = getUtility(IDistributionSet)['debian'].main_archive
-
-    >>> spphs = hoary.getSourcePackagePublishing(
-    ...     PackagePublishingPocket.RELEASE, component_main,
-    ...     warty.main_archive)
-    >>> spphs.count()
-    6
-    >>> for name in sorted(set(
-    ...         pkgpub.sourcepackagerelease.sourcepackagename.name
-    ...         for pkgpub in spphs)):
-    ...     print(name)
-    alsa-utils
-    evolution
-    libstdc++
-    linux-source-2.6.15
-    netapplet
-    pmount
-    >>> hoary.getSourcePackagePublishing(
-    ...     PackagePublishingPocket.RELEASE, component_multiverse,
-    ...     hoary.main_archive).count()
-    0
-    >>> hoary.getSourcePackagePublishing(
-    ...     PackagePublishingPocket.BACKPORTS, component_main,
-    ...     hoary.main_archive).count()
-    0
-    >>> hoary.getSourcePackagePublishing(
-    ...     PackagePublishingPocket.RELEASE, component_main,
-    ...     debian_archive).count()
-    0
-
 ISPP.getPublishedBinaries returns all the binaries generated by the
 publication in question:
 
-    >>> warty = ubuntu['warty']
     >>> warty_pub_source = warty.main_archive.getPublishedSources(
     ...     distroseries=warty, name=u'mozilla-firefox',
     ...     status=PackagePublishingStatus.PUBLISHED).one()
@@ -566,36 +524,6 @@ publication in question:
     >>> print(warty_mozilla_pub_bin.section.name)
     base
 
-DistroSeries.getBinaryPackagePublishing will return
-BinaryPackagePublishingHistory objects for the DistroSeries:
-
-    >>> warty = ubuntu['warty']
-    >>> bpphs = warty.getBinaryPackagePublishing(
-    ...     "i386", PackagePublishingPocket.RELEASE, component_main,
-    ...     warty.main_archive)
-    >>> bpphs.count()
-    8
-    >>> 'mozilla-firefox' in set(
-    ...     pkgpub.binarypackagerelease.binarypackagename.name
-    ...     for pkgpub in bpphs)
-    True
-    >>> warty.getBinaryPackagePublishing(
-    ...     "nope", PackagePublishingPocket.RELEASE, component_main,
-    ...     warty.main_archive).count()
-    0
-    >>> warty.getBinaryPackagePublishing(
-    ...     "i386", PackagePublishingPocket.RELEASE, component_multiverse,
-    ...     warty.main_archive).count()
-    0
-    >>> warty.getBinaryPackagePublishing(
-    ...     "i386", PackagePublishingPocket.BACKPORTS, component_main,
-    ...     warty.main_archive).count()
-    0
-    >>> warty.getBinaryPackagePublishing(
-    ...     "i386", PackagePublishingPocket.RELEASE, component_main,
-    ...     debian_archive).count()
-    0
-
 getAllPublishedSources will return all publications with status PUBLISHED
 and in the main archives for this distroseries:
 
diff --git a/lib/lp/registry/interfaces/distroseries.py b/lib/lp/registry/interfaces/distroseries.py
index d8c6370..ac2959c 100644
--- a/lib/lp/registry/interfaces/distroseries.py
+++ b/lib/lp/registry/interfaces/distroseries.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Interfaces including and related to IDistroSeries."""
@@ -729,26 +729,6 @@ class IDistroSeriesPublic(
     def addSection(section):
         """SQLObject provided method to fill a related join key section."""
 
-    def getBinaryPackagePublishing(archtag, pocket, component, archive):
-        """Get BinaryPackagePublishings in a DistroSeries.
-
-        Can optionally restrict the results by architecturetag, pocket and/or
-        component.
-
-        If archive is passed, restricted the results to the given archive,
-        if it is suppressed the results will be restricted to the
-        distribution 'main_archive'.
-        """
-
-    def getSourcePackagePublishing(pocket, component, archive):
-        """Return a selectResult of ISourcePackagePublishingHistory.
-
-        According status and pocket.
-        If archive is passed, restricted the results to the given archive,
-        if it is suppressed the results will be restricted to the
-        distribution 'main_archive'.
-        """
-
     def searchPackages(text):
         """Search through the package cache for this distroseries and return
         DistroSeriesBinaryPackage objects that match the given text.
diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
index cc209a5..8c3d76c 100644
--- a/lib/lp/registry/model/distroseries.py
+++ b/lib/lp/registry/model/distroseries.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Database classes for a distribution series."""
@@ -12,10 +12,7 @@ __all__ = [
 
 import collections
 from io import BytesIO
-from operator import (
-    attrgetter,
-    itemgetter,
-    )
+from operator import itemgetter
 
 import apt_pkg
 from lazr.delegates import delegate_to
@@ -86,10 +83,6 @@ from lp.registry.model.person import Person
 from lp.registry.model.series import SeriesMixin
 from lp.registry.model.sourcepackage import SourcePackage
 from lp.registry.model.sourcepackagename import SourcePackageName
-from lp.services.database.bulk import (
-    load_referencing,
-    load_related,
-    )
 from lp.services.database.constants import (
     DEFAULT,
     UTC_NOW,
@@ -116,10 +109,7 @@ from lp.services.database.stormexpr import (
     IsTrue,
     )
 from lp.services.librarian.interfaces import ILibraryFileAliasSet
-from lp.services.librarian.model import (
-    LibraryFileAlias,
-    LibraryFileContent,
-    )
+from lp.services.librarian.model import LibraryFileAlias
 from lp.services.mail.signedmessage import signed_message_from_bytes
 from lp.services.propertycache import (
     cachedproperty,
@@ -146,9 +136,7 @@ from lp.soyuz.interfaces.queue import (
 from lp.soyuz.interfaces.sourcepackageformat import (
     ISourcePackageFormatSelectionSet,
     )
-from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
 from lp.soyuz.model.binarypackagename import BinaryPackageName
-from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
 from lp.soyuz.model.component import Component
 from lp.soyuz.model.distributionsourcepackagerelease import (
     DistributionSourcePackageRelease,
@@ -159,10 +147,6 @@ from lp.soyuz.model.distroarchseries import (
     )
 from lp.soyuz.model.distroseriesbinarypackage import DistroSeriesBinaryPackage
 from lp.soyuz.model.distroseriespackagecache import DistroSeriesPackageCache
-from lp.soyuz.model.files import (
-    BinaryPackageFile,
-    SourcePackageReleaseFile,
-    )
 from lp.soyuz.model.publishing import (
     BinaryPackagePublishingHistory,
     get_current_source_releases,
@@ -173,7 +157,6 @@ from lp.soyuz.model.queue import (
     PackageUploadQueue,
     PackageUploadSource,
     )
-from lp.soyuz.model.section import Section
 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
 from lp.translations.enums import LanguagePackType
 from lp.translations.model.distroserieslanguage import (
@@ -1067,80 +1050,6 @@ class DistroSeries(SQLBase, BugTargetBase, HasSpecificationsMixin,
         """See `IDistroSeries`."""
         return self._getAllBinaries().find(scheduleddeletiondate=None)
 
-    def getSourcePackagePublishing(self, pocket, component, archive):
-        """See `IDistroSeries`."""
-        spphs = Store.of(self).find(
-            SourcePackagePublishingHistory,
-            SourcePackagePublishingHistory.archive == archive,
-            SourcePackagePublishingHistory.distroseries == self,
-            SourcePackagePublishingHistory.pocket == pocket,
-            SourcePackagePublishingHistory.component == component,
-            SourcePackagePublishingHistory.status ==
-                PackagePublishingStatus.PUBLISHED,
-            SourcePackagePublishingHistory.sourcepackagename ==
-                SourcePackageName.id).order_by(SourcePackageName.name)
-
-        def eager_load(spphs):
-            # Preload everything which will be used by archivepublisher's
-            # build_source_stanza_fields.
-            load_related(Section, spphs, ["sectionID"])
-            sprs = load_related(
-                SourcePackageRelease, spphs, ["sourcepackagereleaseID"])
-            load_related(SourcePackageName, sprs, ["sourcepackagenameID"])
-            spr_ids = set(map(attrgetter("id"), sprs))
-            sprfs = list(Store.of(self).find(
-                SourcePackageReleaseFile,
-                SourcePackageReleaseFile.sourcepackagereleaseID.is_in(
-                    spr_ids)).order_by(SourcePackageReleaseFile.libraryfileID))
-            file_map = collections.defaultdict(list)
-            for sprf in sprfs:
-                file_map[sprf.sourcepackagerelease].append(sprf)
-            for spr, files in file_map.items():
-                get_property_cache(spr).files = files
-            lfas = load_related(LibraryFileAlias, sprfs, ["libraryfileID"])
-            load_related(LibraryFileContent, lfas, ["contentID"])
-
-        return DecoratedResultSet(spphs, pre_iter_hook=eager_load)
-
-    def getBinaryPackagePublishing(self, archtag, pocket, component, archive):
-        """See `IDistroSeries`."""
-        bpphs = Store.of(self).find(
-            BinaryPackagePublishingHistory,
-            DistroArchSeries.distroseries == self,
-            DistroArchSeries.architecturetag == archtag,
-            BinaryPackagePublishingHistory.archive == archive,
-            BinaryPackagePublishingHistory.distroarchseries ==
-                DistroArchSeries.id,
-            BinaryPackagePublishingHistory.pocket == pocket,
-            BinaryPackagePublishingHistory.component == component,
-            BinaryPackagePublishingHistory.status ==
-                PackagePublishingStatus.PUBLISHED,
-            BinaryPackagePublishingHistory.binarypackagename ==
-                BinaryPackageName.id).order_by(BinaryPackageName.name)
-
-        def eager_load(bpphs):
-            # Preload everything which will be used by archivepublisher's
-            # build_binary_stanza_fields.
-            load_related(Section, bpphs, ["sectionID"])
-            bprs = load_related(
-                BinaryPackageRelease, bpphs, ["binarypackagereleaseID"])
-            bpbs = load_related(BinaryPackageBuild, bprs, ["buildID"])
-            sprs = load_related(
-                SourcePackageRelease, bpbs, ["source_package_release_id"])
-            bpfs = load_referencing(
-                BinaryPackageFile, bprs, ["binarypackagereleaseID"])
-            file_map = collections.defaultdict(list)
-            for bpf in bpfs:
-                file_map[bpf.binarypackagerelease].append(bpf)
-            for bpr, files in file_map.items():
-                get_property_cache(bpr).files = files
-            lfas = load_related(LibraryFileAlias, bpfs, ["libraryfileID"])
-            load_related(LibraryFileContent, lfas, ["contentID"])
-            load_related(SourcePackageName, sprs, ["sourcepackagenameID"])
-            load_related(BinaryPackageName, bprs, ["binarypackagenameID"])
-
-        return DecoratedResultSet(bpphs, pre_iter_hook=eager_load)
-
     def getBuildRecords(self, build_state=None, name=None, pocket=None,
                         arch_tag=None, user=None, binary_only=True):
         """See IHasBuildRecords"""
diff --git a/lib/lp/registry/tests/test_distroseries.py b/lib/lp/registry/tests/test_distroseries.py
index f076ecd..a46cdcd 100644
--- a/lib/lp/registry/tests/test_distroseries.py
+++ b/lib/lp/registry/tests/test_distroseries.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2021 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for distroseries."""
@@ -7,7 +7,6 @@ __all__ = [
     'CurrentSourceReleasesMixin',
     ]
 
-from functools import partial
 import json
 
 from testtools.matchers import Equals
@@ -16,10 +15,6 @@ from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
-from lp.archivepublisher.indices import (
-    build_binary_stanza_fields,
-    build_source_stanza_fields,
-    )
 from lp.registry.errors import NoSuchDistroSeries
 from lp.registry.interfaces.distroseries import IDistroSeriesSet
 from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -46,7 +41,6 @@ from lp.testing import (
     api_url,
     login,
     person_logged_in,
-    record_two_runs,
     StormStatementRecorder,
     TestCase,
     TestCaseWithFactory,
@@ -584,47 +578,6 @@ class TestDistroSeriesPackaging(TestCaseWithFactory):
         expected = ['translatable', 'linked', 'importabletranslatable']
         self.assertEqual(expected, names)
 
-    def test_getSourcePackagePublishing_query_count(self):
-        # Check that the number of queries required to publish source
-        # packages is constant in the number of source packages.
-        def get_index_stanzas():
-            for spp in self.series.getSourcePackagePublishing(
-                    PackagePublishingPocket.RELEASE, self.universe_component,
-                    self.series.main_archive):
-                build_source_stanza_fields(
-                    spp.sourcepackagerelease, spp.component, spp.section)
-
-        recorder1, recorder2 = record_two_runs(
-            get_index_stanzas,
-            partial(
-                self.makeSeriesPackage, pocket=PackagePublishingPocket.RELEASE,
-                status=PackagePublishingStatus.PUBLISHED),
-            5, 5)
-        self.assertThat(recorder1, HasQueryCount(Equals(11)))
-        self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
-
-    def test_getBinaryPackagePublishing_query_count(self):
-        # Check that the number of queries required to publish binary
-        # packages is constant in the number of binary packages.
-        def get_index_stanzas(das):
-            for bpp in self.series.getBinaryPackagePublishing(
-                    das.architecturetag, PackagePublishingPocket.RELEASE,
-                    self.universe_component, self.series.main_archive):
-                build_binary_stanza_fields(
-                    bpp.binarypackagerelease, bpp.component, bpp.section,
-                    bpp.priority, bpp.phased_update_percentage, False)
-
-        das = self.factory.makeDistroArchSeries(distroseries=self.series)
-        recorder1, recorder2 = record_two_runs(
-            partial(get_index_stanzas, das),
-            partial(
-                self.makeSeriesBinaryPackage, das=das,
-                pocket=PackagePublishingPocket.RELEASE,
-                status=PackagePublishingStatus.PUBLISHED),
-            5, 5)
-        self.assertThat(recorder1, HasQueryCount(Equals(15)))
-        self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
-
 
 class TestDistroSeriesWebservice(TestCaseWithFactory):
 
diff --git a/lib/lp/soyuz/interfaces/publishing.py b/lib/lp/soyuz/interfaces/publishing.py
index a1d5dc2..874d1a1 100644
--- a/lib/lp/soyuz/interfaces/publishing.py
+++ b/lib/lp/soyuz/interfaces/publishing.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Publishing interfaces."""
@@ -1133,6 +1133,32 @@ class IPublishingSet(Interface):
             release in the given `archive`, `distroseries`, and `pocket`.
         """
 
+    def getSourcesForPublishing(archive, distroseries, pocket, component):
+        """Get source publications which are published in a given context.
+
+        :param archive: The `Archive` to search.
+        :param distroseries: The `DistroSeries` to search.
+        :param pocket: The `PackagePublishingPocket` to search.
+        :param component: The `Component` to search.
+        :return: A result set of `SourcePackagePublishingHistory` objects in
+            the given context and with the `PUBLISHED` status, ordered by
+            source package name, with associated publisher-relevant objects
+            preloaded.
+        """
+
+    def getBinariesForPublishing(archive, distroarchseries, pocket, component):
+        """Get binary publications which are published in a given context.
+
+        :param archive: The `Archive` to search.
+        :param distroarchseries: The `DistroArchSeries` to search.
+        :param pocket: The `PackagePublishingPocket` to search.
+        :param component: The `Component` to search.
+        :return: A result set of `BinaryPackagePublishingHistory` objects in
+            the given context and with the `PUBLISHED` status, ordered by
+            binary package name, with associated publisher-relevant objects
+            preloaded.
+        """
+
     def getChangesFilesForSources(one_or_more_source_publications):
         """Return all changesfiles for each given source publication.
 
diff --git a/lib/lp/soyuz/model/publishing.py b/lib/lp/soyuz/model/publishing.py
index 421e649..ea0b012 100644
--- a/lib/lp/soyuz/model/publishing.py
+++ b/lib/lp/soyuz/model/publishing.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
@@ -45,6 +45,7 @@ from lp.app.errors import NotFoundError
 from lp.buildmaster.enums import BuildStatus
 from lp.registry.interfaces.person import validate_public_person
 from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.registry.model.sourcepackagename import SourcePackageName
 from lp.services.database import bulk
 from lp.services.database.constants import UTC_NOW
 from lp.services.database.datetimecol import UtcDateTimeCol
@@ -112,6 +113,7 @@ from lp.soyuz.model.files import (
     BinaryPackageFile,
     SourcePackageReleaseFile,
     )
+from lp.soyuz.model.section import Section
 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
 
 
@@ -1511,6 +1513,81 @@ class PublishingSet:
                 active_publishing_status),
             BinaryPackageRelease.architecturespecific == True)
 
+    def getSourcesForPublishing(self, archive, distroseries, pocket,
+                                component):
+        """See `IPublishingSet`."""
+        spphs = IStore(SourcePackagePublishingHistory).find(
+            SourcePackagePublishingHistory,
+            SourcePackagePublishingHistory.archive == archive,
+            SourcePackagePublishingHistory.distroseries == distroseries,
+            SourcePackagePublishingHistory.pocket == pocket,
+            SourcePackagePublishingHistory.component == component,
+            SourcePackagePublishingHistory.status ==
+                PackagePublishingStatus.PUBLISHED,
+            SourcePackagePublishingHistory.sourcepackagename ==
+                SourcePackageName.id).order_by(SourcePackageName.name)
+
+        def eager_load(spphs):
+            # Preload everything which will be used by archivepublisher's
+            # build_source_stanza_fields.
+            bulk.load_related(Section, spphs, ["sectionID"])
+            sprs = bulk.load_related(
+                SourcePackageRelease, spphs, ["sourcepackagereleaseID"])
+            bulk.load_related(SourcePackageName, sprs, ["sourcepackagenameID"])
+            spr_ids = set(map(attrgetter("id"), sprs))
+            sprfs = list(IStore(SourcePackageReleaseFile).find(
+                SourcePackageReleaseFile,
+                SourcePackageReleaseFile.sourcepackagereleaseID.is_in(
+                    spr_ids)).order_by(SourcePackageReleaseFile.libraryfileID))
+            file_map = defaultdict(list)
+            for sprf in sprfs:
+                file_map[sprf.sourcepackagerelease].append(sprf)
+            for spr, files in file_map.items():
+                get_property_cache(spr).files = files
+            lfas = bulk.load_related(
+                LibraryFileAlias, sprfs, ["libraryfileID"])
+            bulk.load_related(LibraryFileContent, lfas, ["contentID"])
+
+        return DecoratedResultSet(spphs, pre_iter_hook=eager_load)
+
+    def getBinariesForPublishing(self, archive, distroarchseries, pocket,
+                                 component):
+        """See `IPublishingSet`."""
+        bpphs = IStore(BinaryPackagePublishingHistory).find(
+            BinaryPackagePublishingHistory,
+            BinaryPackagePublishingHistory.archive == archive,
+            BinaryPackagePublishingHistory.distroarchseries ==
+                distroarchseries,
+            BinaryPackagePublishingHistory.pocket == pocket,
+            BinaryPackagePublishingHistory.component == component,
+            BinaryPackagePublishingHistory.status ==
+                PackagePublishingStatus.PUBLISHED,
+            BinaryPackagePublishingHistory.binarypackagename ==
+                BinaryPackageName.id).order_by(BinaryPackageName.name)
+
+        def eager_load(bpphs):
+            # Preload everything which will be used by archivepublisher's
+            # build_binary_stanza_fields.
+            bulk.load_related(Section, bpphs, ["sectionID"])
+            bprs = bulk.load_related(
+                BinaryPackageRelease, bpphs, ["binarypackagereleaseID"])
+            bpbs = bulk.load_related(BinaryPackageBuild, bprs, ["buildID"])
+            sprs = bulk.load_related(
+                SourcePackageRelease, bpbs, ["source_package_release_id"])
+            bpfs = bulk.load_referencing(
+                BinaryPackageFile, bprs, ["binarypackagereleaseID"])
+            file_map = defaultdict(list)
+            for bpf in bpfs:
+                file_map[bpf.binarypackagerelease].append(bpf)
+            for bpr, files in file_map.items():
+                get_property_cache(bpr).files = files
+            lfas = bulk.load_related(LibraryFileAlias, bpfs, ["libraryfileID"])
+            bulk.load_related(LibraryFileContent, lfas, ["contentID"])
+            bulk.load_related(SourcePackageName, sprs, ["sourcepackagenameID"])
+            bulk.load_related(BinaryPackageName, bprs, ["binarypackagenameID"])
+
+        return DecoratedResultSet(bpphs, pre_iter_hook=eager_load)
+
     def getChangesFilesForSources(self, one_or_more_source_publications):
         """See `IPublishingSet`."""
         # Avoid circular imports.
diff --git a/lib/lp/soyuz/tests/test_publishing.py b/lib/lp/soyuz/tests/test_publishing.py
index fbb19f0..d645ef8 100644
--- a/lib/lp/soyuz/tests/test_publishing.py
+++ b/lib/lp/soyuz/tests/test_publishing.py
@@ -1,9 +1,10 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test native publication workflow for Soyuz. """
 
 import datetime
+from functools import partial
 import io
 import operator
 import os
@@ -20,6 +21,10 @@ from zope.security.proxy import removeSecurityProxy
 from lp.app.errors import NotFoundError
 from lp.archivepublisher.config import getPubConfig
 from lp.archivepublisher.diskpool import DiskPool
+from lp.archivepublisher.indices import (
+    build_binary_stanza_fields,
+    build_source_stanza_fields,
+    )
 from lp.buildmaster.enums import BuildStatus
 from lp.buildmaster.interfaces.processor import IProcessorSet
 from lp.registry.interfaces.distribution import IDistributionSet
@@ -1029,6 +1034,176 @@ class TestPublishingSetLite(TestCaseWithFactory):
             "override the corresponding deb instead.",
             debug_bpph.changeOverride, new_phased_update_percentage=20)
 
+    def makePublishedSourcePackage(self, series, pocket=None, status=None):
+        # Make a published source package.
+        name = self.factory.getUniqueUnicode()
+        sourcepackagename = self.factory.makeSourcePackageName(name)
+        component = getUtility(IComponentSet)["universe"]
+        spph = self.factory.makeSourcePackagePublishingHistory(
+            sourcepackagename=sourcepackagename, distroseries=series,
+            component=component, pocket=pocket, status=status)
+        source_package = self.factory.makeSourcePackage(
+            sourcepackagename=spph.sourcepackagename, distroseries=series)
+        spr = spph.sourcepackagerelease
+        for extension in ("dsc", "tar.gz"):
+            filename = "%s_%s.%s" % (spr.name, spr.version, extension)
+            spr.addFile(self.factory.makeLibraryFileAlias(
+                filename=filename, db_only=True))
+        return source_package
+
+    def test_getSourcesForPublishing(self):
+        # PublisherSet.getSourcesForPublishing returns all the ISPPH records
+        # in a given publishing context.  It is used as part of publishing
+        # some types of archives.
+        # XXX cjwatson 2022-03-28: Detach test from sampledata.
+        ubuntu = getUtility(IDistributionSet)["ubuntu"]
+        hoary = ubuntu["hoary"]
+        component_main = getUtility(IComponentSet)["main"]
+        component_multiverse = getUtility(IComponentSet)["multiverse"]
+        debian_archive = getUtility(IDistributionSet)["debian"].main_archive
+        publishing_set = getUtility(IPublishingSet)
+
+        spphs = publishing_set.getSourcesForPublishing(
+            archive=hoary.main_archive, distroseries=hoary,
+            pocket=PackagePublishingPocket.RELEASE, component=component_main)
+        self.assertEqual(6, spphs.count())
+        self.assertContentEqual(
+            ["alsa-utils", "evolution", "libstdc++", "linux-source-2.6.15",
+             "netapplet", "pmount"],
+            {spph.sourcepackagerelease.name for spph in spphs})
+        self.assertEqual(
+            0,
+            publishing_set.getSourcesForPublishing(
+                archive=hoary.main_archive, distroseries=hoary,
+                pocket=PackagePublishingPocket.RELEASE,
+                component=component_multiverse).count())
+        self.assertEqual(
+            0,
+            publishing_set.getSourcesForPublishing(
+                archive=hoary.main_archive, distroseries=hoary,
+                pocket=PackagePublishingPocket.BACKPORTS,
+                component=component_main).count())
+        self.assertEqual(
+            0,
+            publishing_set.getSourcesForPublishing(
+                archive=debian_archive, distroseries=hoary,
+                pocket=PackagePublishingPocket.RELEASE,
+                component=component_main).count())
+
+    def test_getSourcesForPublishing_query_count(self):
+        # Check that the number of queries required to publish source
+        # packages is constant in the number of source packages.
+        series = self.factory.makeDistroSeries()
+        archive = series.main_archive
+        component_universe = getUtility(IComponentSet)["universe"]
+
+        def get_index_stanzas():
+            for spp in getUtility(IPublishingSet).getSourcesForPublishing(
+                    archive=archive, distroseries=series,
+                    pocket=PackagePublishingPocket.RELEASE,
+                    component=component_universe):
+                build_source_stanza_fields(
+                    spp.sourcepackagerelease, spp.component, spp.section)
+
+        recorder1, recorder2 = record_two_runs(
+            get_index_stanzas,
+            partial(
+                self.makePublishedSourcePackage, series=series,
+                pocket=PackagePublishingPocket.RELEASE,
+                status=PackagePublishingStatus.PUBLISHED),
+            5, 5)
+        self.assertThat(recorder1, HasQueryCount(Equals(8)))
+        self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
+
+    def makePublishedBinaryPackage(self, das, pocket=None, status=None):
+        # Make a published binary package.
+        source = self.makePublishedSourcePackage(
+            das.distroseries, pocket=pocket, status=status)
+        spr = source.distinctreleases[0]
+        binarypackagename = self.factory.makeBinaryPackageName(source.name)
+        bpph = self.factory.makeBinaryPackagePublishingHistory(
+            binarypackagename=binarypackagename, distroarchseries=das,
+            component=spr.component, section_name=spr.section.name,
+            status=status, pocket=pocket, source_package_release=spr)
+        bpr = bpph.binarypackagerelease
+        filename = "%s_%s_%s.deb" % (
+            bpr.name, bpr.version, das.architecturetag)
+        bpr.addFile(self.factory.makeLibraryFileAlias(
+            filename=filename, db_only=True))
+        return bpph
+
+    def test_getBinariesForPublishing(self):
+        # PublisherSet.getBinariesForPublishing returns all the IBPPH
+        # records in a given publishing context.  It is used as part of
+        # publishing some types of archives.
+        # XXX cjwatson 2022-03-28: Detach test from sampledata.
+        ubuntu = getUtility(IDistributionSet)["ubuntu"]
+        warty = ubuntu["warty"]
+        warty_i386 = warty["i386"]
+        warty_another = self.factory.makeDistroArchSeries(distroseries=warty)
+        component_main = getUtility(IComponentSet)["main"]
+        component_multiverse = getUtility(IComponentSet)["multiverse"]
+        debian_archive = getUtility(IDistributionSet)["debian"].main_archive
+        publishing_set = getUtility(IPublishingSet)
+
+        bpphs = publishing_set.getBinariesForPublishing(
+            archive=warty.main_archive, distroarchseries=warty_i386,
+            pocket=PackagePublishingPocket.RELEASE, component=component_main)
+        self.assertEqual(8, bpphs.count())
+        self.assertIn(
+            "mozilla-firefox",
+            {bpph.binarypackagerelease.name for bpph in bpphs})
+        self.assertEqual(
+            0,
+            publishing_set.getBinariesForPublishing(
+                archive=warty.main_archive, distroarchseries=warty_another,
+                pocket=PackagePublishingPocket.RELEASE,
+                component=component_main).count())
+        self.assertEqual(
+            0,
+            publishing_set.getBinariesForPublishing(
+                archive=warty.main_archive, distroarchseries=warty_i386,
+                pocket=PackagePublishingPocket.RELEASE,
+                component=component_multiverse).count())
+        self.assertEqual(
+            0,
+            publishing_set.getBinariesForPublishing(
+                archive=warty.main_archive, distroarchseries=warty_i386,
+                pocket=PackagePublishingPocket.BACKPORTS,
+                component=component_main).count())
+        self.assertEqual(
+            0,
+            publishing_set.getBinariesForPublishing(
+                archive=debian_archive, distroarchseries=warty_i386,
+                pocket=PackagePublishingPocket.RELEASE,
+                component=component_main).count())
+
+    def test_getBinariesForPublishing_query_count(self):
+        # Check that the number of queries required to publish binary
+        # packages is constant in the number of binary packages.
+        das = self.factory.makeDistroArchSeries()
+        archive = das.main_archive
+        component_universe = getUtility(IComponentSet)["universe"]
+
+        def get_index_stanzas():
+            for bpp in getUtility(IPublishingSet).getBinariesForPublishing(
+                    archive=archive, distroarchseries=das,
+                    pocket=PackagePublishingPocket.RELEASE,
+                    component=component_universe):
+                build_binary_stanza_fields(
+                    bpp.binarypackagerelease, bpp.component, bpp.section,
+                    bpp.priority, bpp.phased_update_percentage, False)
+
+        recorder1, recorder2 = record_two_runs(
+            get_index_stanzas,
+            partial(
+                self.makePublishedBinaryPackage, das=das,
+                pocket=PackagePublishingPocket.RELEASE,
+                status=PackagePublishingStatus.PUBLISHED),
+            5, 5)
+        self.assertThat(recorder1, HasQueryCount(Equals(11)))
+        self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
+
 
 class TestSourceDomination(TestNativePublishingBase):
     """Test SourcePackagePublishingHistory.supersede() operates correctly."""