← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Convert Revision and friends to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/424941
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-revision into launchpad:master.
diff --git a/lib/lp/code/browser/branchlisting.py b/lib/lp/code/browser/branchlisting.py
index eee45b8..e4c0c47 100644
--- a/lib/lp/code/browser/branchlisting.py
+++ b/lib/lp/code/browser/branchlisting.py
@@ -383,7 +383,7 @@ class BranchListingItemsMixin:
         # Cache display information for authors of branches' respective
         # last revisions.
         getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-            [revision.revision_author.personID for revision in revisions],
+            [revision.revision_author.person_id for revision in revisions],
             need_icon=True)
 
         # Return a dict keyed on branch id.
diff --git a/lib/lp/code/doc/revision.txt b/lib/lp/code/doc/revision.txt
index 9244c72..3e091bd 100644
--- a/lib/lp/code/doc/revision.txt
+++ b/lib/lp/code/doc/revision.txt
@@ -16,11 +16,15 @@ Interfaces
     >>> from lp.code.model.revision import (
     ...     Revision, RevisionAuthor, RevisionParent, RevisionSet)
     >>> from lp.code.model.branchrevision import BranchRevision
-    >>> verifyObject(IRevision, Revision.get(1))
+    >>> verifyObject(IRevision, IStore(Revision).find(Revision).any())
     True
-    >>> verifyObject(IRevisionAuthor, RevisionAuthor.get(1))
+    >>> verifyObject(
+    ...     IRevisionAuthor,
+    ...     IStore(RevisionAuthor).find(RevisionAuthor).any())
     True
-    >>> verifyObject(IRevisionParent, RevisionParent.get(1))
+    >>> verifyObject(
+    ...     IRevisionParent,
+    ...     IStore(RevisionParent).find(RevisionParent).any())
     True
     >>> verifyObject(IRevisionSet, RevisionSet())
     True
@@ -59,7 +63,6 @@ while the date_created is the time when the database record was created.
     >>> from datetime import datetime
     >>> from pytz import UTC
     >>> date = datetime(2005, 3, 8, 12, 0, tzinfo=UTC)
-    >>> from lp.code.model.revision import Revision
     >>> revision_1 = Revision(log_body=log_body_1,
     ...     revision_author=author, revision_id=revision_id_1,
     ...     revision_date=date)
diff --git a/lib/lp/code/interfaces/revision.py b/lib/lp/code/interfaces/revision.py
index 116e2f8..74d8548 100644
--- a/lib/lp/code/interfaces/revision.py
+++ b/lib/lp/code/interfaces/revision.py
@@ -86,7 +86,7 @@ class IRevisionAuthor(Interface):
     email = Attribute("The email address extracted from the author text.")
     person = PublicPersonChoice(title=_('Author'), required=False,
         readonly=False, vocabulary='ValidPersonOrTeam')
-    personID = Attribute("The primary key of the person")
+    person_id = Attribute("The primary key of the person")
 
     def linkToLaunchpadPerson():
         """Attempt to link the revision author to a Launchpad `Person`.
diff --git a/lib/lp/code/model/branch.py b/lib/lp/code/model/branch.py
index 2b940ba..d538579 100644
--- a/lib/lp/code/model/branch.py
+++ b/lib/lp/code/model/branch.py
@@ -1177,7 +1177,8 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
         tip_revision_id = self.last_scanned_id
         if tip_revision_id is None:
             return None
-        return Revision.selectOneBy(revision_id=tip_revision_id)
+        return IStore(Revision).find(
+            Revision, revision_id=tip_revision_id).one()
 
     def updateScannedDetails(self, db_revision, revision_count):
         """See `IBranch`."""
diff --git a/lib/lp/code/model/revision.py b/lib/lp/code/model/revision.py
index 2f04cd3..ad4ad74 100644
--- a/lib/lp/code/model/revision.py
+++ b/lib/lp/code/model/revision.py
@@ -14,6 +14,7 @@ from datetime import (
     timedelta,
     )
 import email
+from operator import itemgetter
 
 from breezy.revision import NULL_REVISION
 import pytz
@@ -28,10 +29,13 @@ from storm.expr import (
     )
 from storm.locals import (
     Bool,
+    DateTime,
     Int,
     Min,
     Reference,
+    ReferenceSet,
     Storm,
+    Unicode,
     )
 from storm.store import Store
 from zope.component import getUtility
@@ -55,22 +59,12 @@ from lp.services.database.constants import (
     DEFAULT,
     UTC_NOW,
     )
-from lp.services.database.datetimecol import UtcDateTimeCol
+from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.interfaces import (
     IMasterStore,
     IStore,
     )
-from lp.services.database.sqlbase import (
-    quote,
-    SQLBase,
-    )
-from lp.services.database.sqlobject import (
-    BoolCol,
-    ForeignKey,
-    IntCol,
-    SQLMultipleJoin,
-    StringCol,
-    )
+from lp.services.database.stormbase import StormBase
 from lp.services.helpers import shortlist
 from lp.services.identity.interfaces.emailaddress import (
     EmailAddressStatus,
@@ -79,28 +73,44 @@ from lp.services.identity.interfaces.emailaddress import (
 
 
 @implementer(IRevision)
-class Revision(SQLBase):
+class Revision(StormBase):
     """See IRevision."""
 
-    date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
-    log_body = StringCol(notNull=True)
+    __storm_table__ = "Revision"
 
-    revision_author_id = Int(name='revision_author', allow_none=False)
-    revision_author = Reference(revision_author_id, 'RevisionAuthor.id')
+    id = Int(primary=True)
+
+    date_created = DateTime(
+        name="date_created", allow_none=False, default=DEFAULT,
+        tzinfo=pytz.UTC)
+    log_body = Unicode(name="log_body", allow_none=False)
+
+    revision_author_id = Int(name="revision_author", allow_none=False)
+    revision_author = Reference(revision_author_id, "RevisionAuthor.id")
 
-    revision_id = StringCol(notNull=True, alternateID=True,
-                            alternateMethodName='byRevisionID')
-    revision_date = UtcDateTimeCol(notNull=False)
+    revision_id = Unicode(name="revision_id", allow_none=False)
+    revision_date = DateTime(
+        name="revision_date", allow_none=True, tzinfo=pytz.UTC)
 
-    karma_allocated = BoolCol(default=False, notNull=True)
+    karma_allocated = Bool(
+        name="karma_allocated", default=False, allow_none=False)
 
-    properties = SQLMultipleJoin('RevisionProperty', joinColumn='revision')
+    properties = ReferenceSet("id", "RevisionProperty.revision_id")
+
+    def __init__(self, log_body, revision_author, revision_id,
+                 revision_date=None, date_created=DEFAULT):
+        super().__init__()
+        self.log_body = log_body
+        self.revision_author = revision_author
+        self.revision_id = revision_id
+        self.revision_date = revision_date
+        self.date_created = date_created
 
     @property
     def parents(self):
         """See IRevision.parents"""
-        return shortlist(RevisionParent.selectBy(
-            revision=self, orderBy='sequence'))
+        return shortlist(IStore(RevisionParent).find(
+            RevisionParent, revision=self).order_by(RevisionParent.sequence))
 
     @property
     def parent_ids(self):
@@ -172,18 +182,32 @@ class Revision(SQLBase):
             result_set.order_by(Asc(BranchRevision.sequence))
         else:
             result_set.order_by(
-                Branch.ownerID != self.revision_author.personID,
+                Branch.ownerID != self.revision_author.person_id,
                 Asc(BranchRevision.sequence))
 
         return result_set.first()
 
 
 @implementer(IRevisionAuthor)
-class RevisionAuthor(SQLBase):
+class RevisionAuthor(StormBase):
+
+    __storm_table__ = "RevisionAuthor"
+
+    id = Int(primary=True)
+
+    name = Unicode(name="name", allow_none=False)
+
+    email = Unicode(name="email", allow_none=True, default=None)
 
-    _table = 'RevisionAuthor'
+    person_id = Int(
+        name="person", allow_none=True, validator=validate_public_person,
+        default=None)
+    person = Reference(person_id, "Person.id")
 
-    name = StringCol(notNull=True, alternateID=True)
+    def __init__(self, name, email=None):
+        super().__init__()
+        self.name = name
+        self.email = email
 
     @property
     def name_without_email(self):
@@ -196,10 +220,6 @@ class RevisionAuthor(SQLBase):
             return self.name
         return email.utils.parseaddr(self.name)[0]
 
-    email = StringCol(notNull=False, default=None)
-    person = ForeignKey(dbName='person', foreignKey='Person', notNull=False,
-                        storm_validator=validate_public_person, default=None)
-
     def linkToLaunchpadPerson(self):
         """See `IRevisionAuthor`."""
         if self.person is not None or self.email is None:
@@ -210,42 +230,59 @@ class RevisionAuthor(SQLBase):
             return False
         # Only accept an email address that is validated.
         if lp_email.status != EmailAddressStatus.NEW:
-            self.personID = lp_email.personID
+            self.person_id = lp_email.personID
             return True
         else:
             return False
 
 
 @implementer(IRevisionParent)
-class RevisionParent(SQLBase):
+class RevisionParent(StormBase):
     """The association between a revision and its parent."""
 
-    _table = 'RevisionParent'
+    __storm_table__ = "RevisionParent"
+
+    id = Int(primary=True)
+
+    revision_id = Int(name="revision", allow_none=False)
+    revision = Reference(revision_id, "Revision.id")
 
-    revision = ForeignKey(
-        dbName='revision', foreignKey='Revision', notNull=True)
+    sequence = Int(allow_none=False)
+    parent_id = Unicode(allow_none=False)
 
-    sequence = IntCol(notNull=True)
-    parent_id = StringCol(notNull=True)
+    def __init__(self, revision, sequence, parent_id):
+        super().__init__()
+        self.revision = revision
+        self.sequence = sequence
+        self.parent_id = parent_id
 
 
 @implementer(IRevisionProperty)
-class RevisionProperty(SQLBase):
+class RevisionProperty(StormBase):
     """A property on a revision. See IRevisionProperty."""
 
-    _table = 'RevisionProperty'
+    __storm_table__ = "RevisionProperty"
+
+    id = Int(primary=True)
+
+    revision_id = Int(name="revision", allow_none=False)
+    revision = Reference(revision_id, "Revision.id")
+
+    name = Unicode(allow_none=False)
+    value = Unicode(allow_none=False)
 
-    revision = ForeignKey(
-        dbName='revision', foreignKey='Revision', notNull=True)
-    name = StringCol(notNull=True)
-    value = StringCol(notNull=True)
+    def __init__(self, revision, name, value):
+        super().__init__()
+        self.revision = revision
+        self.name = name
+        self.value = value
 
 
 @implementer(IRevisionSet)
 class RevisionSet:
 
     def getByRevisionId(self, revision_id):
-        return Revision.selectOneBy(revision_id=revision_id)
+        return IStore(Revision).find(Revision, revision_id=revision_id).one()
 
     def _createRevisionAuthor(self, revision_author):
         """Extract out the email and check to see if it matches a Person."""
@@ -254,8 +291,11 @@ class RevisionSet:
         if '@' not in email_address:
             email_address = None
 
+        store = IMasterStore(RevisionAuthor)
         author = RevisionAuthor(name=revision_author, email=email_address)
+        store.add(author)
         author.linkToLaunchpadPerson()
+        store.flush()
         return author
 
     def new(self, revision_id, log_body, revision_date, revision_author,
@@ -395,12 +435,12 @@ class RevisionSet:
                 parent_data.append((db_id, sequence, parent_id))
         # Create all RevisionParent objects.
         create((
-            RevisionParent.revisionID, RevisionParent.sequence,
+            RevisionParent.revision_id, RevisionParent.sequence,
             RevisionParent.parent_id), parent_data)
 
         # Create all RevisionProperty objects.
         create((
-            RevisionProperty.revisionID, RevisionProperty.name,
+            RevisionProperty.revision_id, RevisionProperty.name,
             RevisionProperty.value), property_data)
 
     @staticmethod
@@ -413,15 +453,20 @@ class RevisionSet:
 
     def getTipRevisionsForBranches(self, branches):
         """See `IRevisionSet`."""
+        # Circular import.
+        from lp.code.model.branch import Branch
+
         # If there are no branch_ids, then return None.
         branch_ids = [branch.id for branch in branches]
         if not branch_ids:
             return None
-        return Revision.select("""
-            Branch.id in %s AND
-            Revision.revision_id = Branch.last_scanned_id
-            """ % quote(branch_ids),
-            clauseTables=['Branch'], prejoins=['revision_author'])
+        return DecoratedResultSet(
+            IStore(Revision).find(
+                (Revision, RevisionAuthor),
+                Branch.id.is_in(branch_ids),
+                Revision.revision_id == Branch.last_scanned_id,
+                Revision.revision_author_id == RevisionAuthor.id),
+            result_decorator=itemgetter(0))
 
     @staticmethod
     def getRecentRevisionsForProduct(product, days):
@@ -475,7 +520,7 @@ class RevisionSet:
         if person.is_team:
             origin.append(
                 Join(TeamParticipation,
-                     RevisionAuthor.personID == TeamParticipation.personID))
+                     RevisionAuthor.person_id == TeamParticipation.personID))
             person_condition = TeamParticipation.team == person
         else:
             person_condition = RevisionAuthor.person == person
@@ -623,7 +668,7 @@ class RevisionCache(Storm):
     revision_author_id = Int(name='revision_author', allow_none=False)
     revision_author = Reference(revision_author_id, 'RevisionAuthor.id')
 
-    revision_date = UtcDateTimeCol(notNull=True)
+    revision_date = DateTime(allow_none=False, tzinfo=pytz.UTC)
 
     product_id = Int(name='product', allow_none=True)
     product = Reference(product_id, 'Product.id')
diff --git a/lib/lp/code/model/revisioncache.py b/lib/lp/code/model/revisioncache.py
index d43096c..593805b 100644
--- a/lib/lp/code/model/revisioncache.py
+++ b/lib/lp/code/model/revisioncache.py
@@ -74,7 +74,7 @@ class GenericRevisionCollection:
         # is different.
         author = Func(
             'coalesce',
-            RevisionAuthor.personID,
+            RevisionAuthor.person_id,
             SQL(0) - RevisionAuthor.id)
         expressions = [
             RevisionCache.revision_author == RevisionAuthor.id]
@@ -140,7 +140,7 @@ class GenericRevisionCollection:
         if person.is_team:
             query = [
                 TeamParticipation.team == person,
-                RevisionAuthor.personID == TeamParticipation.personID]
+                RevisionAuthor.person_id == TeamParticipation.personID]
         else:
             query = [RevisionAuthor.person == person]
 
diff --git a/lib/lp/code/model/tests/test_revision.py b/lib/lp/code/model/tests/test_revision.py
index bdd0f7a..2061884 100644
--- a/lib/lp/code/model/tests/test_revision.py
+++ b/lib/lp/code/model/tests/test_revision.py
@@ -248,9 +248,9 @@ class TestRevisionSet(TestCaseWithFactory):
         self.assertEqual({'prop1': 'foo'}, rev_1.getProperties())
         rev_2 = self.revision_set.getByRevisionId('rev-2')
         self.assertEqual(['rev-1'], rev_2.parent_ids)
-        # Really, less than 9 is great, but if the count improves, we should
+        # Really, less than 8 is great, but if the count improves, we should
         # tighten this restriction.
-        self.assertThat(recorder, HasQueryCount(Equals(8)))
+        self.assertThat(recorder, HasQueryCount(Equals(7)))
 
     def test_acquireRevisionAuthors(self):
         # AcquireRevisionAuthors creates new authors only if none exists with
diff --git a/lib/lp/code/model/tests/test_revisionauthor.py b/lib/lp/code/model/tests/test_revisionauthor.py
index 9941c7f..0e033f5 100644
--- a/lib/lp/code/model/tests/test_revisionauthor.py
+++ b/lib/lp/code/model/tests/test_revisionauthor.py
@@ -12,6 +12,7 @@ from lp.code.model.revision import (
     )
 from lp.registry.interfaces.person import IPersonSet
 from lp.scripts.garbo import RevisionAuthorEmailLinker
+from lp.services.database.interfaces import IStore
 from lp.services.identity.interfaces.emailaddress import EmailAddressStatus
 from lp.services.log.logger import DevNullLogger
 from lp.testing import TestCase
@@ -139,7 +140,8 @@ class TestNewlyValidatedEmailsLinkRevisionAuthors(MakeHarryTestCase):
             self.author = RevisionSet()._createRevisionAuthor(
                 '"Harry Potter" <harry@xxxxxxxxxxxxx>')
         # Reget the revision author as we have crossed a transaction boundary.
-        self.author = RevisionAuthor.byName(self.author.name)
+        self.author = IStore(RevisionAuthor).find(
+            RevisionAuthor, name=self.author.name).one()
 
     def test_validated_email_updates(self):
         # A newly validated email for a user.
diff --git a/lib/lp/codehosting/scanner/tests/test_bzrsync.py b/lib/lp/codehosting/scanner/tests/test_bzrsync.py
index 6d6fba5..216b400 100644
--- a/lib/lp/codehosting/scanner/tests/test_bzrsync.py
+++ b/lib/lp/codehosting/scanner/tests/test_bzrsync.py
@@ -364,7 +364,8 @@ class TestBzrSync(BzrSyncTestCase):
         author = "Another Author <another@xxxxxxxxxxx>"
         self.commitRevision(committer=author)
         self.syncAndCount(new_revisions=1, new_numbers=1, new_authors=1)
-        db_author = RevisionAuthor.selectOneBy(name=author)
+        db_author = IStore(RevisionAuthor).find(
+            RevisionAuthor, name=author).one()
         self.assertEqual(db_author.name, author)
 
     def test_new_parent(self):
@@ -399,8 +400,8 @@ class TestBzrSync(BzrSyncTestCase):
                             timestamp=1000000000.0, timezone=28800)
         self.syncAndCount(
             new_revisions=2, new_numbers=2, new_parents=1, new_authors=2)
-        rev_1 = Revision.selectOneBy(revision_id='rev-1')
-        rev_2 = Revision.selectOneBy(revision_id='rev-2')
+        rev_1 = IStore(Revision).find(Revision, revision_id="rev-1").one()
+        rev_2 = IStore(Revision).find(Revision, revision_id="rev-2").one()
         UTC = pytz.timezone('UTC')
         dt = datetime.datetime.fromtimestamp(1000000000.0, UTC)
         self.assertEqual(rev_1.revision_date, dt)
diff --git a/lib/lp/scripts/garbo.py b/lib/lp/scripts/garbo.py
index ff17b49..90736eb 100644
--- a/lib/lp/scripts/garbo.py
+++ b/lib/lp/scripts/garbo.py
@@ -910,7 +910,7 @@ class RevisionAuthorEmailLinker(TunableLoop):
         result = self.author_store.find(
             RevisionAuthor,
             RevisionAuthor.id >= self.next_author_id,
-            RevisionAuthor.personID == None,
+            RevisionAuthor.person_id == None,
             RevisionAuthor.email != None)
         result.order_by(RevisionAuthor.id)
         authors = list(result[:chunk_size])
@@ -931,10 +931,10 @@ class RevisionAuthorEmailLinker(TunableLoop):
 
         if emails:
             for author in authors:
-                personID = emails.get(author.email.lower())
-                if personID is None:
+                person_id = emails.get(author.email.lower())
+                if person_id is None:
                     continue
-                author.personID = personID
+                author.person_id = person_id
 
         self.next_author_id = authors[-1].id + 1
         transaction.commit()