← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:stormify-binarypackagerelease into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:stormify-binarypackagerelease into launchpad:master.

Commit message:
Convert BinaryPackageRelease to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/446851
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-binarypackagerelease into launchpad:master.
diff --git a/lib/lp/archivepublisher/domination.py b/lib/lp/archivepublisher/domination.py
index a8df607..b8a8eeb 100644
--- a/lib/lp/archivepublisher/domination.py
+++ b/lib/lp/archivepublisher/domination.py
@@ -340,7 +340,7 @@ def find_live_binary_versions_pass_2(sorted_pubs, cache):
     bpbs = load_related(
         BinaryPackageBuild,
         [pub.binarypackagerelease for pub in arch_indep_pubs],
-        ["buildID"],
+        ["build_id"],
     )
     load_related(SourcePackageRelease, bpbs, ["source_package_release_id"])
 
@@ -655,7 +655,7 @@ class Dominator:
         )
         main_clauses = bpph_location_clauses + [
             BPR.id == BPPH.binarypackagerelease_id,
-            BPR.binarypackagenameID.is_in(candidate_binary_names),
+            BPR.binarypackagename_id.is_in(candidate_binary_names),
             BPR.binpackageformat != BinaryPackageFormat.DDEB,
         ]
 
diff --git a/lib/lp/archivepublisher/model/ftparchive.py b/lib/lp/archivepublisher/model/ftparchive.py
index 3492a7a..2745cb1 100644
--- a/lib/lp/archivepublisher/model/ftparchive.py
+++ b/lib/lp/archivepublisher/model/ftparchive.py
@@ -361,7 +361,7 @@ class FTPArchiveHandler:
             Join(
                 BinaryPackageName,
                 BinaryPackageName.id
-                == BinaryPackageRelease.binarypackagenameID,
+                == BinaryPackageRelease.binarypackagename_id,
             ),
             Join(
                 DistroArchSeries,
@@ -715,7 +715,7 @@ class FTPArchiveHandler:
             == BinaryPackagePublishingHistory.binarypackagerelease_id,
             BinaryPackageFile.binarypackagerelease_id
             == BinaryPackagePublishingHistory.binarypackagerelease_id,
-            BinaryPackageBuild.id == BinaryPackageRelease.buildID,
+            BinaryPackageBuild.id == BinaryPackageRelease.build_id,
             SourcePackageName.id == BinaryPackageBuild.source_package_name_id,
             LibraryFileAlias.id == BinaryPackageFile.libraryfile_id,
             DistroArchSeries.id
diff --git a/lib/lp/archiveuploader/tests/test_uploadprocessor.py b/lib/lp/archiveuploader/tests/test_uploadprocessor.py
index 1c4c37f..665bc0b 100644
--- a/lib/lp/archiveuploader/tests/test_uploadprocessor.py
+++ b/lib/lp/archiveuploader/tests/test_uploadprocessor.py
@@ -1161,8 +1161,10 @@ class TestUploadProcessor(StatsMixin, TestUploadProcessorBase):
 
         # Find the binarypackagerelease and check its component.
         foocomm_binname = BinaryPackageName.selectOneBy(name="foocomm")
-        foocomm_bpr = BinaryPackageRelease.selectOneBy(
-            binarypackagename=foocomm_binname
+        foocomm_bpr = (
+            IStore(BinaryPackageRelease)
+            .find(BinaryPackageRelease, binarypackagename=foocomm_binname)
+            .one()
         )
         self.assertEqual(foocomm_bpr.component.name, "partner")
 
diff --git a/lib/lp/soyuz/browser/build.py b/lib/lp/soyuz/browser/build.py
index 91e1e4e..354c852 100644
--- a/lib/lp/soyuz/browser/build.py
+++ b/lib/lp/soyuz/browser/build.py
@@ -215,8 +215,9 @@ class BuildView(LaunchpadView):
         """Whether or not binaries were already published for this build."""
         # Binaries imported by gina (missing `PackageUpload` record)
         # are always published.
-        imported_binaries = self.package_upload is None and bool(
-            self.context.binarypackages
+        imported_binaries = (
+            self.package_upload is None
+            and not self.context.binarypackages.is_empty()
         )
         # Binaries uploaded from the buildds are published when the
         # corresponding `PackageUpload` status is DONE.
diff --git a/lib/lp/soyuz/browser/queue.py b/lib/lp/soyuz/browser/queue.py
index 7f12bd3..d187d9f 100644
--- a/lib/lp/soyuz/browser/queue.py
+++ b/lib/lp/soyuz/browser/queue.py
@@ -223,7 +223,7 @@ class QueueItemsView(LaunchpadView):
             SourcePackageRelease, puses, ["sourcepackagerelease_id"]
         )
         bpbs = load_related(BinaryPackageBuild, pubs, ["build_id"])
-        bprs = load_referencing(BinaryPackageRelease, bpbs, ["buildID"])
+        bprs = load_referencing(BinaryPackageRelease, bpbs, ["build_id"])
         source_files = load_referencing(
             SourcePackageReleaseFile, source_sprs, ["sourcepackagerelease_id"]
         )
diff --git a/lib/lp/soyuz/browser/tests/binarypackagerelease-views.rst b/lib/lp/soyuz/browser/tests/binarypackagerelease-views.rst
index 126cf7e..3b1089f 100644
--- a/lib/lp/soyuz/browser/tests/binarypackagerelease-views.rst
+++ b/lib/lp/soyuz/browser/tests/binarypackagerelease-views.rst
@@ -2,10 +2,13 @@ BinaryPackageRelease Pages
 ==========================
 
     >>> from zope.component import getMultiAdapter
+    >>> from lp.services.database.interfaces import IStore
     >>> from lp.services.webapp.servers import LaunchpadTestRequest
     >>> from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
 
-    >>> pmount_bin = BinaryPackageRelease.get(15)
+    >>> pmount_bin = IStore(BinaryPackageRelease).get(
+    ...     BinaryPackageRelease, 15
+    ... )
     >>> print(pmount_bin.name)
     pmount
     >>> print(pmount_bin.version)
diff --git a/lib/lp/soyuz/doc/binarypackagerelease.rst b/lib/lp/soyuz/doc/binarypackagerelease.rst
index 6417eae..ffa2f3a 100644
--- a/lib/lp/soyuz/doc/binarypackagerelease.rst
+++ b/lib/lp/soyuz/doc/binarypackagerelease.rst
@@ -5,12 +5,15 @@ BinaryPackageRelease stores unique versions of binarypackagenames
 across build records.
 
     >>> from lp.testing import verifyObject
+    >>> from lp.services.database.interfaces import IStore
     >>> from lp.soyuz.interfaces.binarypackagerelease import (
     ...     IBinaryPackageRelease,
     ... )
     >>> from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
 
-    >>> firefox_bin_release = BinaryPackageRelease.get(12)
+    >>> firefox_bin_release = IStore(BinaryPackageRelease).get(
+    ...     BinaryPackageRelease, 12
+    ... )
     >>> verifyObject(IBinaryPackageRelease, firefox_bin_release)
     True
 
@@ -42,7 +45,9 @@ be quickly ascertained through a set operation.
     ...     IBinaryPackageNameSet,
     ... )
     >>> foobar_name = getUtility(IBinaryPackageNameSet)["foobar"]
-    >>> pmount_bin_release = BinaryPackageRelease.get(20)
+    >>> pmount_bin_release = IStore(BinaryPackageRelease).get(
+    ...     BinaryPackageRelease, 20
+    ... )
     >>> name_ids = (
     ...     foobar_name.id,
     ...     pmount_bin_release.binarypackagename.id,
diff --git a/lib/lp/soyuz/doc/distroarchseriesbinarypackagerelease.rst b/lib/lp/soyuz/doc/distroarchseriesbinarypackagerelease.rst
index 2138dde..c307b56 100644
--- a/lib/lp/soyuz/doc/distroarchseriesbinarypackagerelease.rst
+++ b/lib/lp/soyuz/doc/distroarchseriesbinarypackagerelease.rst
@@ -1,6 +1,7 @@
 Distro Arch Release Binary Package Release
 ==========================================
 
+    >>> from lp.services.database.interfaces import IStore
     >>> from lp.soyuz.model.distroarchseriesbinarypackagerelease import (
     ...     DistroArchSeriesBinaryPackageRelease as DARBPR,
     ... )
@@ -16,11 +17,11 @@ Grab the relevant DARs and BPRs:
     >>> print(hoary.distroseries.name)
     hoary
 
-    >>> mf = BinaryPackageRelease.get(12)
+    >>> mf = IStore(BinaryPackageRelease).get(BinaryPackageRelease, 12)
     >>> print(mf.binarypackagename.name)
     mozilla-firefox
 
-    >>> pm = BinaryPackageRelease.get(15)
+    >>> pm = IStore(BinaryPackageRelease).get(BinaryPackageRelease, 15)
     >>> print(pm.binarypackagename.name)
     pmount
 
diff --git a/lib/lp/soyuz/doc/gina-multiple-arch.rst b/lib/lp/soyuz/doc/gina-multiple-arch.rst
index fa9ca09..3d16759 100644
--- a/lib/lp/soyuz/doc/gina-multiple-arch.rst
+++ b/lib/lp/soyuz/doc/gina-multiple-arch.rst
@@ -22,7 +22,9 @@ Get the current counts of stuff in the database:
     >>> orig_person_count = Person.select().count()
     >>> orig_tp_count = TeamParticipation.select().count()
     >>> orig_email_count = EmailAddress.select().count()
-    >>> orig_bpr_count = BinaryPackageRelease.select().count()
+    >>> orig_bpr_count = (
+    ...     IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
+    ... )
     >>> orig_build_count = BinaryPackageBuild.select().count()
     >>> orig_sbpph_count = IStore(SBPPH).find(SBPPH).count()
 
@@ -145,7 +147,10 @@ There are 4 binary packages generated by the two builds of the two
 source packages. We should only be publishing them into one
 distroarchseries:
 
-    >>> BinaryPackageRelease.select().count() - orig_bpr_count
+    >>> (
+    ...     IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
+    ...     - orig_bpr_count
+    ... )
     4
     >>> BinaryPackageBuild.select().count() - orig_build_count
     2
@@ -168,8 +173,14 @@ Check that the source package was correctly imported:
 And that one of the packages in main is here too:
 
     >>> n = BinaryPackageName.selectOneBy(name="libgadu-dev")
-    >>> ekg = BinaryPackageRelease.selectOneBy(
-    ...     binarypackagenameID=n.id, version="1:1.5-4ubuntu1.2"
+    >>> ekg = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(
+    ...         BinaryPackageRelease,
+    ...         binarypackagename=n,
+    ...         version="1:1.5-4ubuntu1.2",
+    ...     )
+    ...     .one()
     ... )
     >>> print(ekg.section.name)
     libdevel
@@ -186,8 +197,14 @@ component name.
 
     >>> from lp.soyuz.enums import PackagePublishingPriority
     >>> n = BinaryPackageName.selectOneBy(name="ekg")
-    >>> ekg = BinaryPackageRelease.selectOneBy(
-    ...     binarypackagenameID=n.id, version="1:1.5-4ubuntu1.2"
+    >>> ekg = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(
+    ...         BinaryPackageRelease,
+    ...         binarypackagename=n,
+    ...         version="1:1.5-4ubuntu1.2",
+    ...     )
+    ...     .one()
     ... )
     >>> print(ekg.section.name)
     net
@@ -203,8 +220,12 @@ right place, updates the component, and creates it with a semi-bogus
 DSC.
 
     >>> n = BinaryPackageName.selectOneBy(name="bdftopcf")
-    >>> ekg = BinaryPackageRelease.selectOneBy(
-    ...     binarypackagenameID=n.id, version="0.99.0-1"
+    >>> ekg = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(
+    ...         BinaryPackageRelease, binarypackagename=n, version="0.99.0-1"
+    ...     )
+    ...     .one()
     ... )
     >>> print(ekg.section.name)
     x11
diff --git a/lib/lp/soyuz/doc/gina.rst b/lib/lp/soyuz/doc/gina.rst
index 405cf53..c225f5c 100644
--- a/lib/lp/soyuz/doc/gina.rst
+++ b/lib/lp/soyuz/doc/gina.rst
@@ -29,7 +29,9 @@ Get the current counts of stuff in the database:
     >>> orig_person_count = Person.select().count()
     >>> orig_tp_count = TeamParticipation.select().count()
     >>> orig_email_count = EmailAddress.select().count()
-    >>> orig_bpr_count = BinaryPackageRelease.select().count()
+    >>> orig_bpr_count = (
+    ...     IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
+    ... )
     >>> orig_build_count = BinaryPackageBuild.select().count()
     >>> orig_sbpph_count = IStore(SBPPH).find(SBPPH).count()
     >>> orig_sspph_main_count = (
@@ -422,7 +424,10 @@ We have 23 binary packages in breezy. db1-compat, ed, the 3 libcap packages
 and python-pam is unchanged.  python-sqlite fails. The 5 ubuntu-meta packages
 work.
 
-    >>> BinaryPackageRelease.select().count() - orig_bpr_count
+    >>> (
+    ...     IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
+    ...     - orig_bpr_count
+    ... )
     40
     >>> BinaryPackageBuild.select().count() - orig_build_count
     13
@@ -434,7 +439,11 @@ expected:
 
     >>> from lp.soyuz.model.binarypackagename import BinaryPackageName
     >>> n = BinaryPackageName.selectOneBy(name="rioutil")
-    >>> rio = BinaryPackageRelease.selectOneBy(binarypackagenameID=n.id)
+    >>> rio = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(BinaryPackageRelease, binarypackagename=n)
+    ...     .one()
+    ... )
     >>> print(rio.shlibdeps)
     librioutil 1 rioutil
     >>> print(rio.version)
@@ -446,7 +455,11 @@ Test all the data got to the ed BPR intact, and that the missing
 priority was correctly munged to "extra":
 
     >>> n = BinaryPackageName.selectOneBy(name="ed")
-    >>> ed = BinaryPackageRelease.selectOneBy(binarypackagenameID=n.id)
+    >>> ed = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(BinaryPackageRelease, binarypackagename=n)
+    ...     .one()
+    ... )
     >>> print(ed.version)
     0.2-20
     >>> print(ed.build.processor.name)
@@ -480,8 +493,12 @@ Check binary package libgjc-dev in Breezy. Its version number must differ from
 its source version number.
 
     >>> n = BinaryPackageName.selectOneBy(name="libgcj-dev")
-    >>> lib = BinaryPackageRelease.selectOneBy(
-    ...     binarypackagenameID=n.id, version="4:4.0.1-3"
+    >>> lib = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(
+    ...         BinaryPackageRelease, binarypackagename=n, version="4:4.0.1-3"
+    ...     )
+    ...     .one()
     ... )
     >>> print(lib.version)
     4:4.0.1-3
@@ -493,8 +510,10 @@ its source version number.
 Check if the udeb was properly parsed and identified:
 
     >>> n = BinaryPackageName.selectOneBy(name="archive-copier")
-    >>> ac = BinaryPackageRelease.selectOneBy(
-    ...     binarypackagenameID=n.id, version="0.1.5"
+    >>> ac = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(BinaryPackageRelease, binarypackagename=n, version="0.1.5")
+    ...     .one()
     ... )
     >>> print(ac.version)
     0.1.5
@@ -512,8 +531,12 @@ Check if the udeb was properly parsed and identified:
 We check that the binary package publishing override facility works:
 
     >>> n = BinaryPackageName.selectOneBy(name="libdb1-compat")
-    >>> db1 = BinaryPackageRelease.selectOneBy(
-    ...     binarypackagenameID=n.id, version="2.1.3-7"
+    >>> db1 = (
+    ...     IStore(BinaryPackageRelease)
+    ...     .find(
+    ...         BinaryPackageRelease, binarypackagename=n, version="2.1.3-7"
+    ...     )
+    ...     .one()
     ... )
     >>> for pub in (
     ...     IStore(BinaryPackagePublishingHistory)
@@ -645,7 +668,10 @@ changed, etc.
     13
     >>> print(EmailAddress.select().count() - orig_email_count)
     13
-    >>> BinaryPackageRelease.select().count() - orig_bpr_count
+    >>> (
+    ...     IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
+    ...     - orig_bpr_count
+    ... )
     40
     >>> BinaryPackageBuild.select().count() - orig_build_count
     13
diff --git a/lib/lp/soyuz/interfaces/binarypackagerelease.py b/lib/lp/soyuz/interfaces/binarypackagerelease.py
index 8ed59ff..22a0685 100644
--- a/lib/lp/soyuz/interfaces/binarypackagerelease.py
+++ b/lib/lp/soyuz/interfaces/binarypackagerelease.py
@@ -34,7 +34,7 @@ class BinaryPackageReleaseNameLinkageError(ValueError):
 class IBinaryPackageRelease(Interface):
     id = Int(title=_("ID"), required=True)
     binarypackagename = Int(required=True)
-    binarypackagenameID = Int(required=True)
+    binarypackagename_id = Int(required=True)
     version = TextLine(required=True, constraint=valid_debian_version)
     summary = Text(required=True)
     description = Text(required=True)
diff --git a/lib/lp/soyuz/model/archive.py b/lib/lp/soyuz/model/archive.py
index 505b278..72264d4 100644
--- a/lib/lp/soyuz/model/archive.py
+++ b/lib/lp/soyuz/model/archive.py
@@ -1111,8 +1111,8 @@ class Archive(SQLBase):
             bprs = load_related(
                 BinaryPackageRelease, bpphs, ["binarypackagerelease_id"]
             )
-            load_related(BinaryPackageName, bprs, ["binarypackagenameID"])
-            bpbs = load_related(BinaryPackageBuild, bprs, ["buildID"])
+            load_related(BinaryPackageName, bprs, ["binarypackagename_id"])
+            bpbs = load_related(BinaryPackageBuild, bprs, ["build_id"])
             sprs = load_related(
                 SourcePackageRelease, bpbs, ["source_package_release_id"]
             )
@@ -2171,7 +2171,7 @@ class Archive(SQLBase):
                 BinaryPackagePublishingHistory.binarypackagerelease_id
                 == BinaryPackageRelease.id,
                 Cast(BinaryPackageRelease.version, "text") == version,
-                BinaryPackageBuild.id == BinaryPackageRelease.buildID,
+                BinaryPackageBuild.id == BinaryPackageRelease.build_id,
                 DistroArchSeries.id
                 == BinaryPackageBuild.distro_arch_series_id,
                 DistroArchSeries.architecturetag == archtag,
diff --git a/lib/lp/soyuz/model/binarypackagebuild.py b/lib/lp/soyuz/model/binarypackagebuild.py
index 22ca39a..b71a38d 100644
--- a/lib/lp/soyuz/model/binarypackagebuild.py
+++ b/lib/lp/soyuz/model/binarypackagebuild.py
@@ -81,9 +81,11 @@ from lp.soyuz.interfaces.packageset import IPackagesetSet
 from lp.soyuz.mail.binarypackagebuild import BinaryPackageBuildMailer
 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.files import BinaryPackageFile, SourcePackageReleaseFile
 from lp.soyuz.model.packageset import Packageset
 from lp.soyuz.model.queue import PackageUpload, PackageUploadBuild
+from lp.soyuz.model.section import Section
 
 SCORE_BY_POCKET = {
     PackagePublishingPocket.BACKPORTS: 0,
@@ -417,7 +419,7 @@ class BinaryPackageBuild(PackageBuildMixin, SQLBase):
             (BinaryPackageRelease, BinaryPackageName),
             BinaryPackageRelease.build == self,
             BinaryPackageRelease.binarypackagename == BinaryPackageName.id,
-            BinaryPackageName.id == BinaryPackageRelease.binarypackagenameID,
+            BinaryPackageName.id == BinaryPackageRelease.binarypackagename_id,
         )
         return result.order_by(
             [BinaryPackageName.name, BinaryPackageRelease.id]
@@ -446,15 +448,29 @@ class BinaryPackageBuild(PackageBuildMixin, SQLBase):
     @property
     def binarypackages(self):
         """See `IBuild`."""
-        return BinaryPackageRelease.select(
-            """
-            BinaryPackageRelease.build = %s AND
-            BinaryPackageRelease.binarypackagename = BinaryPackageName.id
-            """
-            % sqlvalues(self),
-            clauseTables=["BinaryPackageName"],
-            orderBy=["BinaryPackageName.name", "BinaryPackageRelease.id"],
-            prejoins=["binarypackagename", "component", "section"],
+        return DecoratedResultSet(
+            Store.of(self)
+            .using(
+                BinaryPackageRelease,
+                LeftJoin(
+                    BinaryPackageName,
+                    BinaryPackageRelease.binarypackagename_id
+                    == BinaryPackageName.id,
+                ),
+                LeftJoin(
+                    Component,
+                    BinaryPackageRelease.component_id == Component.id,
+                ),
+                LeftJoin(
+                    Section, BinaryPackageRelease.section_id == Section.id
+                ),
+            )
+            .find(
+                (BinaryPackageRelease, BinaryPackageName, Component, Section),
+                BinaryPackageRelease.build == self,
+            )
+            .order_by(BinaryPackageName.name, BinaryPackageRelease.id),
+            result_decorator=itemgetter(0),
         )
 
     @property
diff --git a/lib/lp/soyuz/model/binarypackagerelease.py b/lib/lp/soyuz/model/binarypackagerelease.py
index 0d97f44..a434f1b 100644
--- a/lib/lp/soyuz/model/binarypackagerelease.py
+++ b/lib/lp/soyuz/model/binarypackagerelease.py
@@ -8,25 +8,16 @@ __all__ = [
 
 import json
 import re
+from datetime import timezone
 from operator import attrgetter
-from typing import Any
 
-from storm.locals import Date, Int, Reference, Store
+from storm.locals import Bool, Date, DateTime, Int, Reference, Store, Unicode
 from zope.component import getUtility
 from zope.interface import implementer
 
 from lp.app.validators.name import valid_name_pattern as debian_name_pattern
 from lp.services.database.constants import UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.enumcol import DBEnum
-from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase
-from lp.services.database.sqlobject import (
-    BoolCol,
-    ForeignKey,
-    IntCol,
-    StringCol,
-)
 from lp.services.database.stormbase import StormBase
 from lp.services.propertycache import cachedproperty, get_property_cache
 from lp.soyuz.enums import (
@@ -35,13 +26,13 @@ from lp.soyuz.enums import (
     BinarySourceReferenceType,
     PackagePublishingPriority,
 )
+from lp.soyuz.interfaces.binarypackagename import IBinaryPackageName
 from lp.soyuz.interfaces.binarypackagerelease import (
     BinaryPackageReleaseNameLinkageError,
     IBinaryPackageRelease,
     IBinaryPackageReleaseDownloadCount,
 )
 from lp.soyuz.interfaces.binarysourcereference import IBinarySourceReferenceSet
-from lp.soyuz.model.binarypackagename import BinaryPackageName
 from lp.soyuz.model.files import BinaryPackageFile
 
 # https://packaging.python.org/en/latest/specifications/core-metadata/#id6
@@ -60,70 +51,54 @@ wheel_name_pattern = re.compile(
 conda_name_pattern = re.compile(r"^[a-z0-9_][a-z0-9.+_-]*$")
 
 
-def _validate_bpr_name(obj: IBinaryPackageRelease, attr: str, value: Any):
+def _validate_bpr_name(bpr: IBinaryPackageRelease, bpn: IBinaryPackageName):
     """Validate that a BPR's BinaryPackageName is appropriate for its format.
 
     The constraints that apply to binary package names vary depending on the
     package format, so we enforce them when creating a
     `BinaryPackageRelease` since at that point we know the format.
 
-    The interface of this function is as required by Storm's property
-    validator system, although this function is currently called manually by
-    `BinaryPackageRelease.__init__` until such time as we port
-    `BinaryPackageRelease` to the native Storm model style in order that we
-    can control initialization order.
-
-    :param obj: The context `IBinaryPackageRelease`.
-    :param attr: The name of the attribute being checked.
-    :param value: The ID of the `BinaryPackageName` being set.
+    :param bpr: The context `IBinaryPackageRelease`.
+    :param bpn: The `IBinaryPackageName` being set.
     """
-    if not isinstance(value, int):
-        raise AssertionError(
-            "Expected int for BinaryPackageName foreign key reference, got %r"
-            % type(value)
-        )
-
-    name = IStore(BinaryPackageName).get(BinaryPackageName, value).name
-    if obj.binpackageformat == BinaryPackageFormat.WHL:
-        if not wheel_name_pattern.match(name):
+    if bpr.binpackageformat == BinaryPackageFormat.WHL:
+        if not wheel_name_pattern.match(bpn.name):
             raise BinaryPackageReleaseNameLinkageError(
                 "Invalid Python wheel name '%s'; must match /%s/i"
-                % (name, wheel_name_pattern.pattern)
+                % (bpn.name, wheel_name_pattern.pattern)
             )
-    elif obj.binpackageformat in (
+    elif bpr.binpackageformat in (
         BinaryPackageFormat.CONDA_V1,
         BinaryPackageFormat.CONDA_V2,
     ):
-        if not conda_name_pattern.match(name):
+        if not conda_name_pattern.match(bpn.name):
             raise BinaryPackageReleaseNameLinkageError(
                 "Invalid Conda package name '%s'; must match /%s/"
-                % (name, conda_name_pattern.pattern)
+                % (bpn.name, conda_name_pattern.pattern)
             )
     else:
         # Fall back to Launchpad's traditional name validation, which
         # coincides with the rules for Debian-format package names.
-        if not debian_name_pattern.match(name):
+        if not debian_name_pattern.match(bpn.name):
             raise BinaryPackageReleaseNameLinkageError(
                 "Invalid package name '%s'; must match /%s/"
-                % (name, debian_name_pattern.pattern)
+                % (bpn.name, debian_name_pattern.pattern)
             )
 
 
 @implementer(IBinaryPackageRelease)
-class BinaryPackageRelease(SQLBase):
-    _table = "BinaryPackageRelease"
-    binarypackagename = ForeignKey(
-        dbName="binarypackagename",
-        notNull=True,
-        foreignKey="BinaryPackageName",
-    )
-    version = StringCol(dbName="version", notNull=True)
-    summary = StringCol(dbName="summary", notNull=True, default="")
-    description = StringCol(dbName="description", notNull=True)
+class BinaryPackageRelease(StormBase):
+    __storm_table__ = "BinaryPackageRelease"
+
+    id = Int(primary=True)
+    binarypackagename_id = Int(name="binarypackagename", allow_none=False)
+    binarypackagename = Reference(binarypackagename_id, "BinaryPackageName.id")
+    version = Unicode(name="version", allow_none=False)
+    summary = Unicode(name="summary", allow_none=False, default="")
+    description = Unicode(name="description", allow_none=False)
     # DB constraint: exactly one of build and ci_build is non-NULL.
-    build = ForeignKey(
-        dbName="build", foreignKey="BinaryPackageBuild", notNull=False
-    )
+    build_id = Int(name="build", allow_none=True)
+    build = Reference(build_id, "BinaryPackageBuild.id")
     ci_build_id = Int(name="ci_build", allow_none=True)
     ci_build = Reference(ci_build_id, "CIBuild.id")
     binpackageformat = DBEnum(
@@ -139,42 +114,93 @@ class BinaryPackageRelease(SQLBase):
     priority = DBEnum(
         name="priority", allow_none=True, enum=PackagePublishingPriority
     )
-    shlibdeps = StringCol(dbName="shlibdeps")
-    depends = StringCol(dbName="depends")
-    recommends = StringCol(dbName="recommends")
-    suggests = StringCol(dbName="suggests")
-    conflicts = StringCol(dbName="conflicts")
-    replaces = StringCol(dbName="replaces")
-    provides = StringCol(dbName="provides")
-    pre_depends = StringCol(dbName="pre_depends")
-    enhances = StringCol(dbName="enhances")
-    breaks = StringCol(dbName="breaks")
-    essential = BoolCol(dbName="essential", default=False)
-    installedsize = IntCol(dbName="installedsize")
-    architecturespecific = BoolCol(dbName="architecturespecific", notNull=True)
-    homepage = StringCol(dbName="homepage")
-    datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
-    debug_package = ForeignKey(
-        dbName="debug_package", foreignKey="BinaryPackageRelease"
+    shlibdeps = Unicode(name="shlibdeps")
+    depends = Unicode(name="depends")
+    recommends = Unicode(name="recommends")
+    suggests = Unicode(name="suggests")
+    conflicts = Unicode(name="conflicts")
+    replaces = Unicode(name="replaces")
+    provides = Unicode(name="provides")
+    pre_depends = Unicode(name="pre_depends")
+    enhances = Unicode(name="enhances")
+    breaks = Unicode(name="breaks")
+    essential = Bool(name="essential", default=False)
+    installedsize = Int(name="installedsize")
+    architecturespecific = Bool(name="architecturespecific", allow_none=False)
+    homepage = Unicode(name="homepage")
+    datecreated = DateTime(
+        allow_none=False, default=UTC_NOW, tzinfo=timezone.utc
     )
-
-    _user_defined_fields = StringCol(dbName="user_defined_fields")
-
-    def __init__(self, *args, **kwargs):
-        if "user_defined_fields" in kwargs:
-            kwargs["_user_defined_fields"] = json.dumps(
-                kwargs["user_defined_fields"]
-            )
-            del kwargs["user_defined_fields"]
-        super().__init__(*args, **kwargs)
-        # XXX cjwatson 2022-06-21: Ideally we'd set this up as a Storm
-        # validator, but that's difficult to arrange with SQLBase since we
-        # can't guarantee that self.binpackageformat will be set before
-        # self.binarypackagename, so just call it by hand here using the
-        # calling convention for validators.
-        _validate_bpr_name(
-            self, "binarypackagename", self.binarypackagename.id
-        )
+    debug_package_id = Int(name="debug_package")
+    debug_package = Reference(debug_package_id, "BinaryPackageRelease.id")
+
+    _user_defined_fields = Unicode(name="user_defined_fields")
+
+    def __init__(
+        self,
+        binarypackagename,
+        version,
+        binpackageformat,
+        description,
+        architecturespecific,
+        summary="",
+        build=None,
+        ci_build=None,
+        component=None,
+        section=None,
+        priority=None,
+        shlibdeps=None,
+        depends=None,
+        recommends=None,
+        suggests=None,
+        conflicts=None,
+        replaces=None,
+        provides=None,
+        pre_depends=None,
+        enhances=None,
+        breaks=None,
+        essential=False,
+        installedsize=None,
+        homepage=None,
+        debug_package=None,
+        user_defined_fields=None,
+    ):
+        super().__init__()
+        self.binarypackagename = binarypackagename
+        self.version = version
+        self.binpackageformat = binpackageformat
+        self.description = description
+        self.architecturespecific = architecturespecific
+        self.summary = summary
+        self.build = build
+        self.ci_build = ci_build
+        self.component = component
+        self.section = section
+        self.priority = priority
+        self.shlibdeps = shlibdeps
+        self.depends = depends
+        self.recommends = recommends
+        self.suggests = suggests
+        self.conflicts = conflicts
+        self.replaces = replaces
+        self.provides = provides
+        self.pre_depends = pre_depends
+        self.enhances = enhances
+        self.breaks = breaks
+        self.essential = essential
+        self.installedsize = installedsize
+        self.homepage = homepage
+        self.debug_package = debug_package
+        if user_defined_fields is not None:
+            self._user_defined_fields = json.dumps(user_defined_fields)
+
+        # Validate the package name.  We can't use Storm's validator
+        # mechanism for this, because we need to validate the combination of
+        # binpackageformat and binarypackagename, and the way validators
+        # work for references mean that the validator would have to make a
+        # database query while the row is in an incomplete state of
+        # construction.
+        _validate_bpr_name(self, self.binarypackagename)
 
     @cachedproperty
     def built_using_references(self):
diff --git a/lib/lp/soyuz/model/distributionsourcepackagecache.py b/lib/lp/soyuz/model/distributionsourcepackagecache.py
index aba6e2d..29c3481 100644
--- a/lib/lp/soyuz/model/distributionsourcepackagecache.py
+++ b/lib/lp/soyuz/model/distributionsourcepackagecache.py
@@ -235,12 +235,12 @@ class DistributionSourcePackageCache(StormBase):
             all_binaries = list(
                 IStore(BinaryPackageRelease).find(
                     (
-                        BinaryPackageRelease.buildID,
-                        BinaryPackageRelease.binarypackagenameID,
+                        BinaryPackageRelease.build_id,
+                        BinaryPackageRelease.binarypackagename_id,
                         BinaryPackageRelease.summary,
                         BinaryPackageRelease.description,
                     ),
-                    BinaryPackageRelease.buildID.is_in(
+                    BinaryPackageRelease.build_id.is_in(
                         [row[1] for row in all_builds]
                     ),
                 )
diff --git a/lib/lp/soyuz/model/distributionsourcepackagerelease.py b/lib/lp/soyuz/model/distributionsourcepackagerelease.py
index 19eb9f3..d915c18 100644
--- a/lib/lp/soyuz/model/distributionsourcepackagerelease.py
+++ b/lib/lp/soyuz/model/distributionsourcepackagerelease.py
@@ -179,11 +179,11 @@ class DistributionSourcePackageRelease:
             Join(
                 BinaryPackageName,
                 BinaryPackageName.id
-                == BinaryPackageRelease.binarypackagenameID,
+                == BinaryPackageRelease.binarypackagename_id,
             ),
             Join(
                 BinaryPackageBuild,
-                BinaryPackageBuild.id == BinaryPackageRelease.buildID,
+                BinaryPackageBuild.id == BinaryPackageRelease.build_id,
             ),
             LeftJoin(
                 DistroSeriesPackageCache,
@@ -231,7 +231,7 @@ class DistributionSourcePackageRelease:
             BinaryPackageRelease,
             Join(
                 BinaryPackageBuild,
-                BinaryPackageBuild.id == BinaryPackageRelease.buildID,
+                BinaryPackageBuild.id == BinaryPackageRelease.build_id,
             ),
             Join(
                 BinaryPackagePublishingHistory,
@@ -246,7 +246,7 @@ class DistributionSourcePackageRelease:
             Join(
                 BinaryPackageName,
                 BinaryPackageName.id
-                == BinaryPackageRelease.binarypackagenameID,
+                == BinaryPackageRelease.binarypackagename_id,
             ),
         )
         archive_ids = list(self.distribution.all_distro_archive_ids)
diff --git a/lib/lp/soyuz/model/distroarchseries.py b/lib/lp/soyuz/model/distroarchseries.py
index db9c6d8..15bd5ef 100644
--- a/lib/lp/soyuz/model/distroarchseries.py
+++ b/lib/lp/soyuz/model/distroarchseries.py
@@ -6,7 +6,7 @@ __all__ = ["DistroArchSeries", "PocketChroot"]
 import hashlib
 from io import BytesIO
 
-from storm.locals import Int, Join, Or, Reference
+from storm.locals import Int, Join, Or, Reference, ReferenceSet
 from storm.store import EmptyResultSet
 from zope.component import getUtility
 from zope.interface import implementer
@@ -25,7 +25,6 @@ from lp.services.database.sqlobject import (
     ForeignKey,
     IntCol,
     SQLObjectNotFound,
-    SQLRelatedJoin,
     StringCol,
 )
 from lp.services.database.stormexpr import fti_search, rank_by_fti
@@ -74,11 +73,11 @@ class DistroArchSeries(SQLBase):
     package_count = IntCol(notNull=True, default=DEFAULT)
     enabled = BoolCol(notNull=False, default=True)
 
-    packages = SQLRelatedJoin(
-        "BinaryPackageRelease",
-        joinColumn="distroarchseries",
-        intermediateTable="BinaryPackagePublishing",
-        otherColumn="binarypackagerelease",
+    packages = ReferenceSet(
+        "<primary key>",
+        "BinaryPackagePublishingHistory.distroarchseries_id",
+        "BinaryPackagePublishingHistory.binarypackagerelease_id",
+        "BinaryPackageRelease.id",
     )
 
     def __getitem__(self, name):
diff --git a/lib/lp/soyuz/model/distroseriespackagecache.py b/lib/lp/soyuz/model/distroseriespackagecache.py
index 6c09b45..45a4692 100644
--- a/lib/lp/soyuz/model/distroseriespackagecache.py
+++ b/lib/lp/soyuz/model/distroseriespackagecache.py
@@ -149,7 +149,7 @@ class DistroSeriesPackageCache(StormBase):
             IStore(BinaryPackageRelease)
             .find(
                 (
-                    BinaryPackageRelease.binarypackagenameID,
+                    BinaryPackageRelease.binarypackagename_id,
                     BinaryPackageRelease.summary,
                     BinaryPackageRelease.description,
                     Max(BinaryPackageRelease.datecreated),
@@ -172,12 +172,12 @@ class DistroSeriesPackageCache(StormBase):
                 ),
             )
             .group_by(
-                BinaryPackageRelease.binarypackagenameID,
+                BinaryPackageRelease.binarypackagename_id,
                 BinaryPackageRelease.summary,
                 BinaryPackageRelease.description,
             )
             .order_by(
-                BinaryPackageRelease.binarypackagenameID,
+                BinaryPackageRelease.binarypackagename_id,
                 Desc(Max(BinaryPackageRelease.datecreated)),
             )
         )
diff --git a/lib/lp/soyuz/model/publishing.py b/lib/lp/soyuz/model/publishing.py
index 4d27562..470032d 100644
--- a/lib/lp/soyuz/model/publishing.py
+++ b/lib/lp/soyuz/model/publishing.py
@@ -392,7 +392,7 @@ class SourcePackagePublishingHistory(StormBase, ArchivePublisherBase):
                 == DistroArchSeries.id,
                 BinaryPackagePublishingHistory.archive == self.archive_id,
                 BinaryPackagePublishingHistory.pocket == self.pocket,
-                BinaryPackageBuild.id == BinaryPackageRelease.buildID,
+                BinaryPackageBuild.id == BinaryPackageRelease.build_id,
                 BinaryPackageBuild.source_package_release_id
                 == self.sourcepackagerelease_id,
                 DistroArchSeries.distroseriesID == self.distroseries_id,
@@ -1449,7 +1449,7 @@ class PublishingSet:
             .find(
                 (
                     BinaryPackagePublishingHistory.distroarchseries_id,
-                    BinaryPackageRelease.binarypackagenameID,
+                    BinaryPackageRelease.binarypackagename_id,
                     BinaryPackageRelease.version,
                 ),
                 BinaryPackagePublishingHistory.pocket == pocket,
@@ -1473,7 +1473,7 @@ class PublishingSet:
         needed = [
             (das, bpr, overrides)
             for (das, bpr, overrides) in expanded
-            if (das.id, bpr.binarypackagenameID, bpr.version)
+            if (das.id, bpr.binarypackagename_id, bpr.version)
             not in already_published
         ]
         if not needed:
@@ -1853,7 +1853,7 @@ class PublishingSet:
             SourcePackagePublishingHistory.sourcepackagerelease_id
             == BinaryPackageBuild.source_package_release_id,
             BinaryPackageRelease.build == BinaryPackageBuild.id,
-            BinaryPackageRelease.binarypackagenameID == BinaryPackageName.id,
+            BinaryPackageRelease.binarypackagename_id == BinaryPackageName.id,
             SourcePackagePublishingHistory.distroseries_id
             == DistroArchSeries.distroseriesID,
             BinaryPackagePublishingHistory.distroarchseries_id
@@ -1933,7 +1933,7 @@ class PublishingSet:
             LibraryFileContent.id == LibraryFileAlias.contentID,
             LibraryFileAlias.id == BinaryPackageFile.libraryfile_id,
             BinaryPackageFile.binarypackagerelease == BinaryPackageRelease.id,
-            BinaryPackageRelease.buildID == BinaryPackageBuild.id,
+            BinaryPackageRelease.build_id == BinaryPackageBuild.id,
             SourcePackagePublishingHistory.sourcepackagerelease_id
             == BinaryPackageBuild.source_package_release_id,
             BinaryPackagePublishingHistory.binarypackagerelease_id
@@ -2141,7 +2141,7 @@ class PublishingSet:
             bprs = bulk.load_related(
                 BinaryPackageRelease, bpphs, ["binarypackagerelease_id"]
             )
-            bpbs = bulk.load_related(BinaryPackageBuild, bprs, ["buildID"])
+            bpbs = bulk.load_related(BinaryPackageBuild, bprs, ["build_id"])
             sprs = bulk.load_related(
                 SourcePackageRelease, bpbs, ["source_package_release_id"]
             )
@@ -2158,7 +2158,9 @@ class PublishingSet:
             )
             bulk.load_related(LibraryFileContent, lfas, ["contentID"])
             bulk.load_related(SourcePackageName, sprs, ["sourcepackagenameID"])
-            bulk.load_related(BinaryPackageName, bprs, ["binarypackagenameID"])
+            bulk.load_related(
+                BinaryPackageName, bprs, ["binarypackagename_id"]
+            )
 
         return DecoratedResultSet(bpphs, pre_iter_hook=eager_load)
 
@@ -2377,7 +2379,7 @@ class PublishingSet:
             Join(
                 debug_bpph,
                 debug_bpph.binarypackagerelease_id
-                == BinaryPackageRelease.debug_packageID,
+                == BinaryPackageRelease.debug_package_id,
             ),
         ]
         return (
diff --git a/lib/lp/soyuz/model/queue.py b/lib/lp/soyuz/model/queue.py
index fd7b594..e686aa4 100644
--- a/lib/lp/soyuz/model/queue.py
+++ b/lib/lp/soyuz/model/queue.py
@@ -1882,8 +1882,8 @@ def prefill_packageupload_caches(uploads, puses, pubs, pucs, logs):
     binary_sprs = load_related(
         SourcePackageRelease, bpbs, ["source_package_release_id"]
     )
-    bprs = load_referencing(BinaryPackageRelease, bpbs, ["buildID"])
-    load_related(BinaryPackageName, bprs, ["binarypackagenameID"])
+    bprs = load_referencing(BinaryPackageRelease, bpbs, ["build_id"])
+    load_related(BinaryPackageName, bprs, ["binarypackagename_id"])
     sprs = source_sprs + binary_sprs
 
     load_related(SourcePackageName, sprs, ["sourcepackagenameID"])
diff --git a/lib/lp/soyuz/scripts/gina/handlers.py b/lib/lp/soyuz/scripts/gina/handlers.py
index fd62506..5af9a8b 100644
--- a/lib/lp/soyuz/scripts/gina/handlers.py
+++ b/lib/lp/soyuz/scripts/gina/handlers.py
@@ -862,8 +862,8 @@ class BinaryPackageHandler:
         to_upload = check_not_in_librarian([fname], bin.archive_root, fdir)
         fname, path = to_upload[0]
 
-        componentID = self.distro_handler.getComponentByName(bin.component).id
-        sectionID = self.distro_handler.ensureSection(bin.section).id
+        component = self.distro_handler.getComponentByName(bin.component)
+        section = self.distro_handler.ensureSection(bin.section)
         architecturespecific = bin.architecture != "all"
 
         bin_name = getUtility(IBinaryPackageNameSet).ensure(bin.package)
@@ -874,14 +874,14 @@ class BinaryPackageHandler:
         if bin._user_defined:
             kwargs["user_defined_fields"] = bin._user_defined
         binpkg = BinaryPackageRelease(
-            binarypackagename=bin_name.id,
-            component=componentID,
+            binarypackagename=bin_name,
+            component=component,
             version=bin.version,
             description=bin.description,
             summary=bin.summary,
             build=build.id,
             binpackageformat=getBinaryPackageFormat(bin.filename),
-            section=sectionID,
+            section=section,
             priority=prioritymap[bin.priority],
             shlibdeps=bin.shlibs,
             depends=bin.depends,
diff --git a/lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.rst b/lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.rst
index 47887ea..076a1b3 100644
--- a/lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.rst
+++ b/lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.rst
@@ -74,6 +74,7 @@ permission to manipulate them.
     >>> from lp.registry.interfaces.sourcepackagename import (
     ...     ISourcePackageNameSet,
     ... )
+    >>> from lp.services.database.interfaces import IStore
     >>> from lp.soyuz.interfaces.binarypackagename import (
     ...     IBinaryPackageNameSet,
     ... )
@@ -92,11 +93,12 @@ permission to manipulate them.
     ...     sourcepackagename=alsa_utils
     ... ):
     ...     source.component = universe
-    >>> for binary in BinaryPackageRelease.selectBy(binarypackagename=pmount):
+    >>> for binary in IStore(BinaryPackageRelease).find(
+    ...     BinaryPackageRelease, binarypackagename=pmount
+    ... ):
     ...     binary.component = universe
-    ...
-    >>> for binary in BinaryPackageRelease.selectBy(
-    ...     binarypackagename=mozilla
+    >>> for binary in IStore(BinaryPackageRelease).find(
+    ...     BinaryPackageRelease, binarypackagename=mozilla
     ... ):
     ...     binary.component = universe
     >>> import transaction
diff --git a/lib/lp/soyuz/templates/build-index.pt b/lib/lp/soyuz/templates/build-index.pt
index 4910e03..e00428b 100644
--- a/lib/lp/soyuz/templates/build-index.pt
+++ b/lib/lp/soyuz/templates/build-index.pt
@@ -33,7 +33,7 @@
 
       </div> <!-- yui-g  -->
 
-      <tal:binaries condition="context/binarypackages">
+      <tal:binaries condition="not: context/binarypackages/is_empty">
 
         <div id="binaries" class="portlet">
           <div metal:use-macro="template/macros/binaries" />