95
98
:param tables: A dict of Storm tables to the Join expression. If an
96
99
expression in branch_filter_expressions refers to a table, then
97
100
that table *must* be in this list.
101
:param asymmetric_filter_expressions: As per branch_filter_expressions
102
but only applies to one side of reflexive joins.
103
:param asymmetric_tables: As per tables, for
104
asymmetric_filter_expressions.
99
106
self._store = store
100
107
if branch_filter_expressions is None:
101
108
branch_filter_expressions = []
102
self._branch_filter_expressions = branch_filter_expressions
109
self._branch_filter_expressions = list(branch_filter_expressions)
103
110
if tables is None:
105
112
self._tables = tables
113
if asymmetric_filter_expressions is None:
114
asymmetric_filter_expressions = []
115
self._asymmetric_filter_expressions = list(
116
asymmetric_filter_expressions)
117
if asymmetric_tables is None:
118
asymmetric_tables = {}
119
self._asymmetric_tables = asymmetric_tables
106
120
if exclude_from_search is None:
107
121
exclude_from_search = []
108
122
self._exclude_from_search = exclude_from_search
134
148
return self._store
136
150
def _filterBy(self, expressions, table=None, join=None,
137
exclude_from_search=None):
138
"""Return a subset of this collection, filtered by 'expressions'."""
151
exclude_from_search=None, symmetric=True):
152
"""Return a subset of this collection, filtered by 'expressions'.
154
:param symmetric: If True this filter will apply to both sides of merge
155
proposal lookups and any other lookups that join Branch back onto
139
158
# NOTE: JonathanLange 2009-02-17: We might be able to avoid the need
140
159
# for explicit 'tables' by harnessing Storm's table inference system.
141
160
# See http://paste.ubuntu.com/118711/ for one way to do that.
142
tables = self._tables.copy()
143
161
if table is not None:
145
163
raise InvalidFilter("Cannot specify a table without a join.")
164
if expressions is None:
166
tables = self._tables.copy()
167
asymmetric_tables = self._asymmetric_tables.copy()
169
if table is not None:
171
symmetric_expr = self._branch_filter_expressions + expressions
172
asymmetric_expr = list(self._asymmetric_filter_expressions)
174
if table is not None:
175
asymmetric_tables[table] = join
176
symmetric_expr = list(self._branch_filter_expressions)
177
asymmetric_expr = self._asymmetric_filter_expressions + expressions
147
178
if exclude_from_search is None:
148
179
exclude_from_search = []
149
if expressions is None:
151
180
return self.__class__(
153
self._branch_filter_expressions + expressions,
155
self._exclude_from_search + exclude_from_search)
184
self._exclude_from_search + exclude_from_search,
157
188
def _getBranchIdQuery(self):
158
189
"""Return a Storm 'Select' for the branch IDs in this collection."""
223
264
def getMergeProposals(self, statuses=None, for_branches=None,
224
265
target_branch=None, merged_revnos=None):
225
266
"""See `IBranchCollection`."""
226
Target = ClassAlias(Branch, "target")
227
tables = [Branch] + self._tables.values() + [
228
Join(BranchMergeProposal, And(
229
Branch.id==BranchMergeProposal.source_branchID,
230
*self._branch_filter_expressions)),
231
Join(Target, Target.id==BranchMergeProposal.target_branchID)
233
expressions = self._getBranchVisibilityExpression()
234
expressions.extend(self._getBranchVisibilityExpression(Target))
267
# teams = SQL("teams as (SELECT team from teamparticipation where person=%s)" % sqlvalues
268
scope_tables = [Branch] + self._tables.values()
269
scope_expressions = self._branch_filter_expressions
270
select = self.store.using(*scope_tables).find(
271
(Branch.id, Branch.private, Branch.ownerID), *scope_expressions)
272
branches_query = select._get_select()
273
with_expr = [With("scope_branches", branches_query)
274
] + self._getCandidateBranchesWith()
275
expressions = [SQL("""
276
source_branch IN (SELECT id FROM candidate_branches) AND
277
target_branch IN (SELECT id FROM candidate_branches)""")]
278
tables = [BranchMergeProposal]
279
if self._asymmetric_filter_expressions:
280
# Need to filter on Branch beyond the with constraints.
281
expressions += self._asymmetric_filter_expressions
283
BranchMergeProposal.source_branchID == Branch.id)
284
tables.append(Branch)
285
tables.extend(self._asymmetric_tables.values())
235
286
if for_branches is not None:
236
287
branch_ids = [branch.id for branch in for_branches]
237
288
expressions.append(
533
588
if person is None:
534
589
return AnonymousBranchCollection(
535
590
self._store, self._branch_filter_expressions,
536
self._tables, self._exclude_from_search)
591
self._tables, self._exclude_from_search,
592
self._asymmetric_filter_expressions, self._asymmetric_tables)
537
593
return VisibleBranchCollection(
538
594
person, self._store, self._branch_filter_expressions,
539
self._tables, self._exclude_from_search)
595
self._tables, self._exclude_from_search,
596
self._asymmetric_filter_expressions, self._asymmetric_tables)
541
598
def withBranchType(self, *branch_types):
542
return self._filterBy([Branch.branch_type.is_in(branch_types)])
599
return self._filterBy([Branch.branch_type.is_in(branch_types)],
544
602
def withLifecycleStatus(self, *statuses):
545
603
"""See `IBranchCollection`."""
546
return self._filterBy([Branch.lifecycle_status.is_in(statuses)])
604
return self._filterBy([Branch.lifecycle_status.is_in(statuses)],
548
607
def modifiedSince(self, epoch):
549
608
"""See `IBranchCollection`."""
550
return self._filterBy([Branch.date_last_modified > epoch])
609
return self._filterBy([Branch.date_last_modified > epoch],
552
612
def scannedSince(self, epoch):
553
613
"""See `IBranchCollection`."""
554
return self._filterBy([Branch.last_scanned > epoch])
614
return self._filterBy([Branch.last_scanned > epoch], symmetric=False)
557
617
class AnonymousBranchCollection(GenericBranchCollection):
558
618
"""Branch collection that only shows public branches."""
560
def __init__(self, store=None, branch_filter_expressions=None,
561
tables=None, exclude_from_search=None):
562
super(AnonymousBranchCollection, self).__init__(
564
branch_filter_expressions=list(branch_filter_expressions),
565
tables=tables, exclude_from_search=exclude_from_search)
567
620
def _getBranchVisibilityExpression(self, branch_class=Branch):
568
621
"""Return the where clauses for visibility."""
569
622
return [branch_class.private == False]
624
def _getCandidateBranchesWith(self):
625
"""Return WITH clauses defining candidate branches.
627
These are defined in terms of scope_branches which should be separately
630
# Anonymous users get public branches only.
632
With("candidate_branches",
633
SQL("select id from scope_branches where not private"))
572
637
class VisibleBranchCollection(GenericBranchCollection):
573
638
"""A branch collection that has special logic for visibility."""
575
640
def __init__(self, user, store=None, branch_filter_expressions=None,
576
tables=None, exclude_from_search=None):
641
tables=None, exclude_from_search=None,
642
asymmetric_filter_expressions=None, asymmetric_tables=None):
577
643
super(VisibleBranchCollection, self).__init__(
578
644
store=store, branch_filter_expressions=branch_filter_expressions,
579
tables=tables, exclude_from_search=exclude_from_search)
645
tables=tables, exclude_from_search=exclude_from_search,
646
asymmetric_filter_expressions=asymmetric_filter_expressions,
647
asymmetric_tables=asymmetric_tables)
580
648
self._user = user
581
649
self._private_branch_ids = self._getPrivateBranchSubQuery()
583
651
def _filterBy(self, expressions, table=None, join=None,
584
exclude_from_search=None):
585
"""Return a subset of this collection, filtered by 'expressions'."""
652
exclude_from_search=None, symmetric=True):
653
"""Return a subset of this collection, filtered by 'expressions'.
655
:param symmetric: If True this filter will apply to both sides of merge
656
proposal lookups and any other lookups that join Branch back onto
586
659
# NOTE: JonathanLange 2009-02-17: We might be able to avoid the need
587
660
# for explicit 'tables' by harnessing Storm's table inference system.
588
661
# See http://paste.ubuntu.com/118711/ for one way to do that.
589
tables = self._tables.copy()
590
662
if table is not None:
592
664
raise InvalidFilter("Cannot specify a table without a join.")
665
if expressions is None:
667
tables = self._tables.copy()
668
asymmetric_tables = self._asymmetric_tables.copy()
670
if table is not None:
672
symmetric_expr = self._branch_filter_expressions + expressions
673
asymmetric_expr = list(self._asymmetric_filter_expressions)
675
if table is not None:
676
asymmetric_tables[table] = join
677
symmetric_expr = list(self._branch_filter_expressions)
678
asymmetric_expr = self._asymmetric_filter_expressions + expressions
594
679
if exclude_from_search is None:
595
680
exclude_from_search = []
596
if expressions is None:
598
681
return self.__class__(
601
self._branch_filter_expressions + expressions,
603
self._exclude_from_search + exclude_from_search)
686
self._exclude_from_search + exclude_from_search,
605
690
def _getPrivateBranchSubQuery(self):
606
691
"""Return a subquery to get the private branches the user can see.
651
736
branch_class.id.is_in(self._private_branch_ids))
652
737
return [public_or_private]
739
def _getCandidateBranchesWith(self):
740
"""Return WITH clauses defining candidate branches.
742
These are defined in terms of scope_branches which should be separately
747
# Really an anonymous sitation
749
With("candidate_branches",
750
SQL("select id from scope_branches where not private"))
753
With("teams", self.store.find(TeamParticipation.team,
754
TeamParticipation.personID == person)._get_select()),
755
With("private_branches", SQL("""
756
SELECT scope_branches.id FROM scope_branches WHERE
757
scope_branches.private AND ((scope_branches.owner in (select team from teams) OR
758
EXISTS(SELECT true from BranchSubscription, teams WHERE
759
branchsubscription.branch = scope_branches.id AND
760
branchsubscription.person = teams.team)))""")),
761
With("candidate_branches", SQL("""
762
(SELECT id FROM private_branches) UNION
763
(select id FROM scope_branches WHERE not private)"""))
654
766
def visibleByUser(self, person):
655
767
"""See `IBranchCollection`."""
656
768
if person == self._user: