~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/code/model/branchcollection.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-08-18 00:32:33 UTC
  • mfrom: (13687.4.9 bug-826692)
  • Revision ID: launchpad@pqm.canonical.com-20110818003233-m7ip8f487fg3ssyu
[r=jtv][bug=826692] Preload related merge proposal data to reduce
        number of queries needed for rendering +activereviews page.

Show diffs side-by-side

added added

removed removed

Lines of Context:
63
63
from lp.code.model.codeimport import CodeImport
64
64
from lp.code.model.codereviewcomment import CodeReviewComment
65
65
from lp.code.model.codereviewvote import CodeReviewVoteReference
 
66
from lp.code.model.diff import (
 
67
    Diff,
 
68
    PreviewDiff,
 
69
    )
66
70
from lp.code.model.seriessourcepackagebranch import SeriesSourcePackageBranch
67
71
from lp.registry.model.distribution import Distribution
68
72
from lp.registry.model.distroseries import DistroSeries
69
73
from lp.registry.model.person import (
70
74
    Owner,
71
75
    Person,
 
76
    ValidPersonCache,
72
77
    )
73
78
from lp.registry.model.product import Product
74
79
from lp.registry.model.sourcepackagename import SourcePackageName
152
157
                  exclude_from_search=None, symmetric=True):
153
158
        """Return a subset of this collection, filtered by 'expressions'.
154
159
 
155
 
        :param symmetric: If True this filter will apply to both sides of merge
156
 
            proposal lookups and any other lookups that join Branch back onto
157
 
            Branch.
 
160
        :param symmetric: If True this filter will apply to both sides
 
161
            of merge proposal lookups and any other lookups that join
 
162
            Branch back onto Branch.
158
163
        """
159
164
        # NOTE: JonathanLange 2009-02-17: We might be able to avoid the need
160
165
        # for explicit 'tables' by harnessing Storm's table inference system.
175
180
            if table is not None:
176
181
                asymmetric_tables[table] = join
177
182
            symmetric_expr = list(self._branch_filter_expressions)
178
 
            asymmetric_expr = self._asymmetric_filter_expressions + expressions
 
183
            asymmetric_expr = (
 
184
                self._asymmetric_filter_expressions + expressions)
179
185
        if exclude_from_search is None:
180
186
            exclude_from_search = []
181
187
        return self.__class__(
205
211
    def _getCandidateBranchesWith(self):
206
212
        """Return WITH clauses defining candidate branches.
207
213
 
208
 
        These are defined in terms of scope_branches which should be separately
209
 
        calculated.
 
214
        These are defined in terms of scope_branches which should be
 
215
        separately calculated.
210
216
        """
211
217
        return [
212
218
            With("candidate_branches", SQL("SELECT id from scope_branches"))]
213
219
 
 
220
    def _preloadDataForBranches(self, branches):
 
221
        """Preload branches cached associated product series and
 
222
        suite source packages."""
 
223
        caches = dict((branch.id, get_property_cache(branch))
 
224
            for branch in branches)
 
225
        branch_ids = caches.keys()
 
226
        for cache in caches.values():
 
227
            if not safe_hasattr(cache, '_associatedProductSeries'):
 
228
                cache._associatedProductSeries = []
 
229
            if not safe_hasattr(cache, '_associatedSuiteSourcePackages'):
 
230
                cache._associatedSuiteSourcePackages = []
 
231
            if not safe_hasattr(cache, 'code_import'):
 
232
                cache.code_import = None
 
233
        # associatedProductSeries
 
234
        # Imported here to avoid circular import.
 
235
        from lp.registry.model.productseries import ProductSeries
 
236
        for productseries in self.store.find(
 
237
            ProductSeries,
 
238
            ProductSeries.branchID.is_in(branch_ids)):
 
239
            cache = caches[productseries.branchID]
 
240
            cache._associatedProductSeries.append(productseries)
 
241
        # associatedSuiteSourcePackages
 
242
        series_set = getUtility(IFindOfficialBranchLinks)
 
243
        # Order by the pocket to get the release one first. If changing
 
244
        # this be sure to also change BranchCollection.getBranches.
 
245
        links = series_set.findForBranches(branches).order_by(
 
246
            SeriesSourcePackageBranch.pocket)
 
247
        for link in links:
 
248
            cache = caches[link.branchID]
 
249
            cache._associatedSuiteSourcePackages.append(
 
250
                link.suite_sourcepackage)
 
251
        for code_import in IStore(CodeImport).find(
 
252
            CodeImport, CodeImport.branchID.is_in(branch_ids)):
 
253
            cache = caches[code_import.branchID]
 
254
            cache.code_import = code_import
 
255
 
214
256
    def getBranches(self, eager_load=False):
215
257
        """See `IBranchCollection`."""
216
258
        all_tables = set(
225
267
            branch_ids = set(branch.id for branch in rows)
226
268
            if not branch_ids:
227
269
                return
228
 
            branches = dict((branch.id, branch) for branch in rows)
229
 
            caches = dict((branch.id, get_property_cache(branch))
230
 
                for branch in rows)
231
 
            for cache in caches.values():
232
 
                if not safe_hasattr(cache, '_associatedProductSeries'):
233
 
                    cache._associatedProductSeries = []
234
 
                if not safe_hasattr(cache, '_associatedSuiteSourcePackages'):
235
 
                    cache._associatedSuiteSourcePackages = []
236
 
                if not safe_hasattr(cache, 'code_import'):
237
 
                    cache.code_import = None
238
 
            # associatedProductSeries
239
 
            # Imported here to avoid circular import.
240
 
            from lp.registry.model.productseries import ProductSeries
241
 
            for productseries in self.store.find(
242
 
                ProductSeries,
243
 
                ProductSeries.branchID.is_in(branch_ids)):
244
 
                cache = caches[productseries.branchID]
245
 
                cache._associatedProductSeries.append(productseries)
246
 
            # associatedSuiteSourcePackages
247
 
            series_set = getUtility(IFindOfficialBranchLinks)
248
 
            # Order by the pocket to get the release one first. If changing
249
 
            # this be sure to also change BranchCollection.getBranches.
250
 
            links = series_set.findForBranches(rows).order_by(
251
 
                SeriesSourcePackageBranch.pocket)
252
 
            for link in links:
253
 
                cache = caches[link.branchID]
254
 
                cache._associatedSuiteSourcePackages.append(
255
 
                    link.suite_sourcepackage)
 
270
            self._preloadDataForBranches(rows)
256
271
            load_related(Product, rows, ['productID'])
257
272
            # So far have only needed the persons for their canonical_url - no
258
273
            # need for validity etc in the /branches API call.
259
274
            load_related(Person, rows,
260
275
                ['ownerID', 'registrantID', 'reviewerID'])
261
 
            for code_import in IStore(CodeImport).find(
262
 
                CodeImport, CodeImport.branchID.is_in(branch_ids)):
263
 
                cache = caches[code_import.branchID]
264
 
                cache.code_import = code_import
265
276
            load_referencing(BugBranch, rows, ['branchID'])
266
277
        return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
267
278
 
268
279
    def getMergeProposals(self, statuses=None, for_branches=None,
269
 
                          target_branch=None, merged_revnos=None):
 
280
                          target_branch=None, merged_revnos=None,
 
281
                          eager_load=False):
270
282
        """See `IBranchCollection`."""
271
283
        if (self._asymmetric_filter_expressions or for_branches or
272
284
            target_branch or merged_revnos):
273
285
            return self._naiveGetMergeProposals(statuses, for_branches,
274
 
                target_branch, merged_revnos)
 
286
                target_branch, merged_revnos, eager_load)
275
287
        else:
276
288
            # When examining merge proposals in a scope, this is a moderately
277
289
            # effective set of constrained queries. It is not effective when
279
291
            return self._scopedGetMergeProposals(statuses)
280
292
 
281
293
    def _naiveGetMergeProposals(self, statuses=None, for_branches=None,
282
 
        target_branch=None, merged_revnos=None):
 
294
        target_branch=None, merged_revnos=None, eager_load=False):
 
295
 
 
296
        def do_eager_load(rows):
 
297
            branch_ids = set()
 
298
            person_ids = set()
 
299
            diff_ids = set()
 
300
            for mp in rows:
 
301
                branch_ids.add(mp.target_branchID)
 
302
                branch_ids.add(mp.source_branchID)
 
303
                person_ids.add(mp.registrantID)
 
304
                person_ids.add(mp.merge_reporterID)
 
305
                diff_ids.add(mp.preview_diff_id)
 
306
            if not branch_ids:
 
307
                return
 
308
 
 
309
            # Pre-load Person and ValidPersonCache.
 
310
            list(self.store.find(
 
311
                (Person, ValidPersonCache),
 
312
                ValidPersonCache.id == Person.id,
 
313
                Person.id.is_in(person_ids),
 
314
                ))
 
315
 
 
316
            # Pre-load PreviewDiffs and Diffs.
 
317
            list(self.store.find(
 
318
                (PreviewDiff, Diff),
 
319
                PreviewDiff.id.is_in(diff_ids),
 
320
                Diff.id == PreviewDiff.diff_id))
 
321
 
 
322
            branches = set(
 
323
                self.store.find(Branch, Branch.id.is_in(branch_ids)))
 
324
            self._preloadDataForBranches(branches)
 
325
 
283
326
        Target = ClassAlias(Branch, "target")
284
327
        extra_tables = list(set(
285
328
            self._tables.values() + self._asymmetric_tables.values()))
286
329
        tables = [Branch] + extra_tables + [
287
330
            Join(BranchMergeProposal, And(
288
 
                Branch.id==BranchMergeProposal.source_branchID,
 
331
                Branch.id == BranchMergeProposal.source_branchID,
289
332
                *(self._branch_filter_expressions +
290
333
                  self._asymmetric_filter_expressions))),
291
 
            Join(Target, Target.id==BranchMergeProposal.target_branchID)
 
334
            Join(Target, Target.id == BranchMergeProposal.target_branchID),
292
335
            ]
293
336
        expressions = self._getBranchVisibilityExpression()
294
337
        expressions.extend(self._getBranchVisibilityExpression(Target))
305
348
        if statuses is not None:
306
349
            expressions.append(
307
350
                BranchMergeProposal.queue_status.is_in(statuses))
308
 
        return self.store.using(*tables).find(BranchMergeProposal, *expressions)
 
351
        resultset = self.store.using(*tables).find(
 
352
            BranchMergeProposal, *expressions)
 
353
        if not eager_load:
 
354
            return resultset
 
355
        else:
 
356
            return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
309
357
 
310
358
    def _scopedGetMergeProposals(self, statuses):
311
359
        scope_tables = [Branch] + self._tables.values()
513
561
        """See `IBranchCollection`."""
514
562
        subquery = Select(
515
563
            TeamParticipation.teamID,
516
 
            where=TeamParticipation.personID==person.id)
 
564
            where=TeamParticipation.personID == person.id)
517
565
        filter = [In(Branch.ownerID, subquery)]
518
566
 
519
567
        return self._filterBy(filter, symmetric=False)
666
714
    def _getCandidateBranchesWith(self):
667
715
        """Return WITH clauses defining candidate branches.
668
716
 
669
 
        These are defined in terms of scope_branches which should be separately
670
 
        calculated.
 
717
        These are defined in terms of scope_branches which should be
 
718
        separately calculated.
671
719
        """
672
720
        # Anonymous users get public branches only.
673
721
        return [
694
742
                  exclude_from_search=None, symmetric=True):
695
743
        """Return a subset of this collection, filtered by 'expressions'.
696
744
 
697
 
        :param symmetric: If True this filter will apply to both sides of merge
698
 
            proposal lookups and any other lookups that join Branch back onto
699
 
            Branch.
 
745
        :param symmetric: If True this filter will apply to both sides
 
746
            of merge proposal lookups and any other lookups that join
 
747
            Branch back onto Branch.
700
748
        """
701
749
        # NOTE: JonathanLange 2009-02-17: We might be able to avoid the need
702
750
        # for explicit 'tables' by harnessing Storm's table inference system.
717
765
            if table is not None:
718
766
                asymmetric_tables[table] = join
719
767
            symmetric_expr = list(self._branch_filter_expressions)
720
 
            asymmetric_expr = self._asymmetric_filter_expressions + expressions
 
768
            asymmetric_expr = (
 
769
                self._asymmetric_filter_expressions + expressions)
721
770
        if exclude_from_search is None:
722
771
            exclude_from_search = []
723
772
        return self.__class__(
781
830
    def _getCandidateBranchesWith(self):
782
831
        """Return WITH clauses defining candidate branches.
783
832
 
784
 
        These are defined in terms of scope_branches which should be separately
785
 
        calculated.
 
833
        These are defined in terms of scope_branches which should be
 
834
        separately calculated.
786
835
        """
787
836
        person = self._user
788
837
        if person is None:
796
845
                TeamParticipation.personID == person.id)._get_select()),
797
846
            With("private_branches", SQL("""
798
847
                SELECT scope_branches.id FROM scope_branches WHERE
799
 
                scope_branches.private AND ((scope_branches.owner in (select team from teams) OR
800
 
                    EXISTS(SELECT true from BranchSubscription, teams WHERE
801
 
                        branchsubscription.branch = scope_branches.id AND
802
 
                        branchsubscription.person = teams.team)))""")),
 
848
                scope_branches.private AND (
 
849
                    (scope_branches.owner in (select team from teams) OR
 
850
                     EXISTS(SELECT true from BranchSubscription, teams WHERE
 
851
                         branchsubscription.branch = scope_branches.id AND
 
852
                         branchsubscription.person = teams.team)))""")),
803
853
            With("candidate_branches", SQL("""
804
854
                (SELECT id FROM private_branches) UNION
805
855
                (select id FROM scope_branches WHERE not private)"""))