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 (
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 (
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'.
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
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.
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.
205
211
def _getCandidateBranchesWith(self):
206
212
"""Return WITH clauses defining candidate branches.
208
These are defined in terms of scope_branches which should be separately
214
These are defined in terms of scope_branches which should be
215
separately calculated.
212
218
With("candidate_branches", SQL("SELECT id from scope_branches"))]
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(
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)
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
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:
228
branches = dict((branch.id, branch) for branch in rows)
229
caches = dict((branch.id, get_property_cache(branch))
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(
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)
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)
268
279
def getMergeProposals(self, statuses=None, for_branches=None,
269
target_branch=None, merged_revnos=None):
280
target_branch=None, merged_revnos=None,
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)
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)
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):
296
def do_eager_load(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)
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),
316
# Pre-load PreviewDiffs and Diffs.
317
list(self.store.find(
319
PreviewDiff.id.is_in(diff_ids),
320
Diff.id == PreviewDiff.diff_id))
323
self.store.find(Branch, Branch.id.is_in(branch_ids)))
324
self._preloadDataForBranches(branches)
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),
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)
356
return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
310
358
def _scopedGetMergeProposals(self, statuses):
311
359
scope_tables = [Branch] + self._tables.values()
694
742
exclude_from_search=None, symmetric=True):
695
743
"""Return a subset of this collection, filtered by 'expressions'.
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
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.
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.
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)"""))