launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #12741
[Merge] lp:~wgrant/launchpad/bug-1056617 into lp:launchpad
William Grant has proposed merging lp:~wgrant/launchpad/bug-1056617 into lp:launchpad.
Commit message:
Fix Milestone.specifications to be fast even when querying workitems.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1056617 in Launchpad itself: "ProductSeries:+index timing out due to workitems"
https://bugs.launchpad.net/launchpad/+bug/1056617
For more details, see:
https://code.launchpad.net/~wgrant/launchpad/bug-1056617/+merge/127412
Milestone.specifications has been rather slow (~150ms) since workitem filtering was added a few months back. This branch rewords it to use a more indexable (<10ms) UNION, and rewrites ProjectMilestone and ProjectGroupMilestoneTag's implementations to use the new common fast query.
It needs a new index on specification(milestone) to be really fast, but it's still substantially faster without it.
A test failure exposed a bug in the old query. If a blueprint had a deleted workitem targeted to the milestone, it would not be included in the result even if the blueprint itself was milestoned.
--
https://code.launchpad.net/~wgrant/launchpad/bug-1056617/+merge/127412
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/bug-1056617 into lp:launchpad.
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py 2012-08-07 02:31:56 +0000
+++ lib/lp/registry/model/milestone.py 2012-10-02 04:47:19 +0000
@@ -28,9 +28,9 @@
from storm.expr import (
And,
Desc,
- Join,
LeftJoin,
- Or,
+ Select,
+ Union,
)
from storm.locals import Store
from storm.zope import IResultSet
@@ -148,7 +148,36 @@
@property
def specifications(self):
- raise NotImplementedError
+ from lp.registry.model.person import Person
+ store = Store.of(self.target)
+ origin = [
+ Specification,
+ LeftJoin(Person, Specification.assigneeID == Person.id),
+ ]
+ milestones = self._milestone_ids_expr
+
+ results = store.using(*origin).find(
+ (Specification, Person),
+ Specification.id.is_in(
+ Union(
+ Select(
+ Specification.id, tables=[Specification],
+ where=(Specification.milestoneID.is_in(milestones))),
+ Select(
+ SpecificationWorkItem.specification_id,
+ tables=[SpecificationWorkItem],
+ where=And(
+ SpecificationWorkItem.milestone_id.is_in(
+ milestones),
+ SpecificationWorkItem.deleted == False)),
+ all=True)))
+ results.config(distinct=True)
+ ordered_results = results.order_by(Desc(Specification.priority),
+ Specification.definition_status,
+ Specification.implementation_status,
+ Specification.title)
+ mapper = lambda row: row[0]
+ return DecoratedResultSet(ordered_results, mapper)
def bugtasks(self, user):
"""The list of non-conjoined bugtasks targeted to this milestone."""
@@ -190,30 +219,8 @@
code_name = StringCol(dbName='codename', notNull=False, default=None)
@property
- def specifications(self):
- from lp.registry.model.person import Person
- store = Store.of(self)
- origin = [
- Specification,
- LeftJoin(
- SpecificationWorkItem,
- SpecificationWorkItem.specification_id == Specification.id),
- LeftJoin(Person, Specification.assigneeID == Person.id),
- ]
-
- results = store.using(*origin).find(
- (Specification, Person),
- Or(Specification.milestoneID == self.id,
- SpecificationWorkItem.milestone_id == self.id),
- Or(SpecificationWorkItem.deleted == None,
- SpecificationWorkItem.deleted == False))
- results.config(distinct=True)
- ordered_results = results.order_by(Desc(Specification.priority),
- Specification.definition_status,
- Specification.implementation_status,
- Specification.title)
- mapper = lambda row: row[0]
- return DecoratedResultSet(ordered_results, mapper)
+ def _milestone_ids_expr(self):
+ return (self.id,)
@property
def target(self):
@@ -421,36 +428,15 @@
self.summary = None
@property
- def specifications(self):
- """See `IMilestoneData`."""
- from lp.registry.model.person import Person
+ def _milestone_ids_expr(self):
from lp.registry.model.product import Product
- store = Store.of(self.target)
- origin = [
- Specification,
- LeftJoin(
- SpecificationWorkItem,
- SpecificationWorkItem.specification_id == Specification.id),
- Join(Milestone,
- Or(Milestone.id == Specification.milestoneID,
- Milestone.id == SpecificationWorkItem.milestone_id)),
- Join(Product, Product.id == Milestone.productID),
- LeftJoin(Person, Specification.assigneeID == Person.id),
- ]
-
- results = store.using(*origin).find(
- (Specification, Person),
- Product.projectID == self.target.id,
- Milestone.name == self.name,
- Or(SpecificationWorkItem.deleted == None,
- SpecificationWorkItem.deleted == False))
- results.config(distinct=True)
- ordered_results = results.order_by(Desc(Specification.priority),
- Specification.definition_status,
- Specification.implementation_status,
- Specification.title)
- mapper = lambda row: row[0]
- return DecoratedResultSet(ordered_results, mapper)
+ return Select(
+ Milestone.id,
+ tables=[Milestone, Product],
+ where=And(
+ Milestone.name == self.name,
+ Milestone.productID == Product.id,
+ Product.project == self.target))
@property
def displayname(self):
=== modified file 'lib/lp/registry/model/milestonetag.py'
--- lib/lp/registry/model/milestonetag.py 2012-09-28 06:25:44 +0000
+++ lib/lp/registry/model/milestonetag.py 2012-10-02 04:47:19 +0000
@@ -10,27 +10,25 @@
]
-from storm.locals import (
+from storm.expr import (
+ And,
+ Exists,
+ Select,
+ )
+from storm.properties import (
DateTime,
Int,
- Reference,
Unicode,
)
-from zope.component import getUtility
+from storm.references import Reference
from zope.interface import implements
-from lp.blueprints.model.specification import Specification
from lp.registry.interfaces.milestonetag import IProjectGroupMilestoneTag
from lp.registry.model.milestone import (
Milestone,
MilestoneData,
)
from lp.registry.model.product import Product
-from lp.services.database.interfaces import (
- DEFAULT_FLAVOR,
- IStoreSelector,
- MAIN_STORE,
- )
class MilestoneTag(object):
@@ -80,20 +78,19 @@
return self.displayname
@property
- def specifications(self):
- """See IMilestoneData."""
- store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
- results = []
- for tag in self.tags:
- result = store.find(
- Specification,
- Specification.milestone == Milestone.id,
- Milestone.product == Product.id,
+ def _milestone_ids_expr(self):
+ tag_constraints = And(*[
+ Exists(
+ Select(
+ 1, tables=[MilestoneTag],
+ where=And(
+ MilestoneTag.milestone_id == Milestone.id,
+ MilestoneTag.tag == tag)))
+ for tag in self.tags])
+ return Select(
+ Milestone.id,
+ tables=[Milestone, Product],
+ where=And(
+ Milestone.productID == Product.id,
Product.project == self.target,
- MilestoneTag.milestone_id == Milestone.id,
- MilestoneTag.tag == tag)
- results.append(result)
- result = results.pop()
- for i in results:
- result = result.intersection(i)
- return result
+ tag_constraints))
=== modified file 'lib/lp/registry/tests/test_milestone.py'
--- lib/lp/registry/tests/test_milestone.py 2012-07-09 08:48:31 +0000
+++ lib/lp/registry/tests/test_milestone.py 2012-10-02 04:47:19 +0000
@@ -201,16 +201,14 @@
return specification, target_milestone
def test_milestones_on_product(self):
- specification, target_milestone = self._create_milestones_on_target(
+ spec, target_milestone = self._create_milestones_on_target(
product=self.factory.makeProduct())
- self.assertEqual([specification],
- list(target_milestone.specifications))
+ self.assertContentEqual([spec], target_milestone.specifications)
def test_milestones_on_distribution(self):
- specification, target_milestone = self._create_milestones_on_target(
+ spec, target_milestone = self._create_milestones_on_target(
distribution=self.factory.makeDistribution())
- self.assertEqual([specification],
- list(target_milestone.specifications))
+ self.assertContentEqual([spec], target_milestone.specifications)
def test_milestones_on_project(self):
# A Project (Project Group) milestone contains all specifications
@@ -218,11 +216,10 @@
# a certain name.
projectgroup = self.factory.makeProject()
product = self.factory.makeProduct(project=projectgroup)
- specification, target_milestone = self._create_milestones_on_target(
+ spec, target_milestone = self._create_milestones_on_target(
product=product)
milestone = projectgroup.getMilestone(name=target_milestone.name)
- self.assertEqual([specification],
- list(milestone.specifications))
+ self.assertContentEqual([spec], milestone.specifications)
def test_milestones_with_deleted_workitems(self):
# Deleted work items do not cause the specification to show up
@@ -230,7 +227,7 @@
milestone = self.factory.makeMilestone(
product=self.factory.makeProduct())
specification = self.factory.makeSpecification(
- milestone=milestone, product=milestone.product)
+ product=milestone.product)
self.factory.makeSpecificationWorkItem(
specification=specification, milestone=milestone, deleted=True)
- self.assertEqual([], list(milestone.specifications))
+ self.assertContentEqual([], milestone.specifications)
Follow ups