launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #21192
[Merge] lp:~cjwatson/launchpad/preload-branch-landing-targets into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/preload-branch-landing-targets into lp:launchpad.
Commit message:
Preload Branch.landing_targets; improve landing_candidates/landing_targets preloading using BranchMergeProposal.preloadDataForBMPs.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/preload-branch-landing-targets/+merge/310651
This adds several more queries to Branch:++branch-pending-merges, but it should be worth it.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/preload-branch-landing-targets into lp:launchpad.
=== modified file 'lib/lp/_schema_circular_imports.py'
--- lib/lp/_schema_circular_imports.py 2016-10-12 23:24:24 +0000
+++ lib/lp/_schema_circular_imports.py 2016-11-11 14:26:47 +0000
@@ -240,7 +240,8 @@
patch_entry_return_type(IBranch, 'getSubscription', IBranchSubscription)
patch_collection_property(
IBranch, '_api_landing_candidates', IBranchMergeProposal)
-patch_collection_property(IBranch, 'landing_targets', IBranchMergeProposal)
+patch_collection_property(
+ IBranch, '_api_landing_targets', IBranchMergeProposal)
patch_plain_parameter_type(IBranch, 'linkBug', 'bug', IBug)
patch_plain_parameter_type(
IBranch, 'linkSpecification', 'spec', ISpecification)
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2016-10-15 13:41:19 +0000
+++ lib/lp/code/browser/branch.py 2016-11-11 14:26:47 +0000
@@ -485,7 +485,8 @@
@cachedproperty
def landing_targets(self):
"""Return a filtered list of landing targets."""
- return latest_proposals_for_each_branch(self.context.landing_targets)
+ targets = self.context.getPrecachedLandingTargets(self.user)
+ return latest_proposals_for_each_branch(targets)
@property
def latest_landing_candidates(self):
=== modified file 'lib/lp/code/browser/tests/test_branch.py'
--- lib/lp/code/browser/tests/test_branch.py 2016-04-06 10:59:15 +0000
+++ lib/lp/code/browser/tests/test_branch.py 2016-11-11 14:26:47 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Unit tests for BranchView."""
@@ -561,7 +561,25 @@
view = create_view(branch, '+index')
with StormStatementRecorder() as recorder:
view.landing_candidates
- self.assertThat(recorder, HasQueryCount(Equals(5)))
+ self.assertThat(recorder, HasQueryCount(Equals(13)))
+
+ def test_query_count_landing_targets(self):
+ product = self.factory.makeProduct()
+ branch = self.factory.makeBranch(product=product)
+ for i in range(10):
+ self.factory.makeBranchMergeProposal(source_branch=branch)
+ stacked = self.factory.makeBranch(product=product)
+ target = self.factory.makeBranch(stacked_on=stacked, product=product)
+ prereq = self.factory.makeBranch(product=product)
+ self.factory.makeBranchMergeProposal(
+ source_branch=branch, target_branch=target,
+ prerequisite_branch=prereq)
+ Store.of(branch).flush()
+ Store.of(branch).invalidate()
+ view = create_view(branch, '+index')
+ with StormStatementRecorder() as recorder:
+ view.landing_targets
+ self.assertThat(recorder, HasQueryCount(Equals(12)))
def test_query_count_subscriber_content(self):
branch = self.factory.makeBranch()
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2016-10-15 02:28:10 +0000
+++ lib/lp/code/interfaces/branch.py 2016-11-11 14:26:47 +0000
@@ -544,14 +544,18 @@
# These attributes actually have a value_type of IBranchMergeProposal,
# but uses Interface to prevent circular imports, and the value_type is
# set near IBranchMergeProposal.
- landing_targets = exported(
+ landing_targets = Attribute(
+ 'A collection of the merge proposals where this branch is '
+ 'the source branch.')
+ _api_landing_targets = exported(
CollectionField(
title=_('Landing Targets'),
description=_(
'A collection of the merge proposals where this branch is '
'the source branch.'),
readonly=True,
- value_type=Reference(Interface)))
+ value_type=Reference(Interface)),
+ exported_as='landing_targets')
landing_candidates = Attribute(
'A collection of the merge proposals where this branch is '
'the target branch.')
@@ -573,6 +577,13 @@
readonly=True,
value_type=Reference(Interface)))
+ def getPrecachedLandingTargets(user):
+ """Return precached landing targets.
+
+ Target and prerequisite branches are preloaded, along with the
+ related chains of stacked-on branches visible to `user`.
+ """
+
def getPrecachedLandingCandidates(user):
"""Return precached landing candidates.
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2016-10-15 02:28:10 +0000
+++ lib/lp/code/model/branch.py 2016-11-11 14:26:47 +0000
@@ -9,6 +9,7 @@
]
from datetime import datetime
+from functools import partial
import operator
from bzrlib import urlutils
@@ -473,8 +474,20 @@
date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
date_last_modified = UtcDateTimeCol(notNull=True, default=DEFAULT)
- landing_targets = SQLMultipleJoin(
- 'BranchMergeProposal', joinColumn='source_branch')
+ @property
+ def landing_targets(self):
+ """See `IBranch`."""
+ return Store.of(self).find(
+ BranchMergeProposal, BranchMergeProposal.source_branch == self)
+
+ def getPrecachedLandingTargets(self, user):
+ """See `IBranch`."""
+ loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user)
+ return DecoratedResultSet(self.landing_targets, pre_iter_hook=loader)
+
+ @property
+ def _api_landing_targets(self):
+ return self.getPrecachedLandingTargets(getUtility(ILaunchBag).user)
@property
def active_landing_targets(self):
@@ -494,17 +507,9 @@
def getPrecachedLandingCandidates(self, user):
"""See `IBranch`."""
- # Circular import.
- from lp.code.model.branchcollection import GenericBranchCollection
-
- def eager_load(rows):
- branches = bulk.load_related(
- Branch, rows, ['source_branchID', 'prerequisite_branchID'])
- GenericBranchCollection.preloadVisibleStackedOnBranches(
- branches, user)
-
+ loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user)
return DecoratedResultSet(
- self.landing_candidates, pre_iter_hook=eager_load)
+ self.landing_candidates, pre_iter_hook=loader)
@property
def _api_landing_candidates(self):
=== modified file 'lib/lp/code/tests/test_branch_webservice.py'
--- lib/lp/code/tests/test_branch_webservice.py 2015-09-30 00:38:30 +0000
+++ lib/lp/code/tests/test_branch_webservice.py 2016-11-11 14:26:47 +0000
@@ -1,13 +1,10 @@
-# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2011-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
from lazr.restfulclient.errors import BadRequest
-from testtools.matchers import (
- Equals,
- LessThan,
- )
+from testtools.matchers import LessThan
from zope.component import getUtility
from zope.security.management import endInteraction
from zope.security.proxy import removeSecurityProxy
@@ -23,6 +20,7 @@
launchpadlib_for,
login_person,
logout,
+ person_logged_in,
record_two_runs,
run_with_login,
TestCaseWithFactory,
@@ -37,20 +35,19 @@
layer = DatabaseFunctionalLayer
def test_landing_candidates_constant_queries(self):
- target = self.factory.makeProduct()
- login_person(target.owner)
- trunk = self.factory.makeBranch(target=target)
- trunk_url = api_url(trunk)
- webservice = webservice_for_person(
- target.owner, permission=OAuthPermission.WRITE_PRIVATE)
- logout()
+ project = self.factory.makeProduct()
+ with person_logged_in(project.owner):
+ trunk = self.factory.makeBranch(target=project)
+ trunk_url = api_url(trunk)
+ webservice = webservice_for_person(
+ project.owner, permission=OAuthPermission.WRITE_PRIVATE)
def create_mp():
with admin_logged_in():
branch = self.factory.makeBranch(
- target=target,
+ target=project,
stacked_on=self.factory.makeBranch(
- target=target,
+ target=project,
information_type=InformationType.PRIVATESECURITY),
information_type=InformationType.PRIVATESECURITY)
self.factory.makeBranchMergeProposal(
@@ -62,11 +59,34 @@
list_mps()
recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2)
self.assertThat(recorder1, HasQueryCount(LessThan(30)))
- # Each new MP triggers a query for Person, BranchMergeProposal
- # and PreviewDiff. Eventually they should be batched, but this
- # at least ensures the situation doesn't get worse.
- self.assertThat(
- recorder2, HasQueryCount(Equals(recorder1.count + (2 * 3))))
+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
+
+ def test_landing_targets_constant_queries(self):
+ project = self.factory.makeProduct()
+ with person_logged_in(project.owner):
+ source = self.factory.makeBranch(target=project)
+ source_url = api_url(source)
+ webservice = webservice_for_person(
+ project.owner, permission=OAuthPermission.WRITE_PRIVATE)
+
+ def create_mp():
+ with admin_logged_in():
+ branch = self.factory.makeBranch(
+ target=project,
+ stacked_on=self.factory.makeBranch(
+ target=project,
+ information_type=InformationType.PRIVATESECURITY),
+ information_type=InformationType.PRIVATESECURITY)
+ self.factory.makeBranchMergeProposal(
+ source_branch=source, target_branch=branch)
+
+ def list_mps():
+ webservice.get(source_url + '/landing_targets')
+
+ list_mps()
+ recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2)
+ self.assertThat(recorder1, HasQueryCount(LessThan(30)))
+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
class TestBranchOperations(TestCaseWithFactory):
Follow ups