93
96
:param tables: A dict of Storm tables to the Join expression. If an
94
97
expression in branch_filter_expressions refers to a table, then
95
98
that table *must* be in this list.
99
:param asymmetric_filter_expressions: As per branch_filter_expressions
100
but only applies to one side of reflexive joins.
101
:param asymmetric_tables: As per tables, for
102
asymmetric_filter_expressions.
97
104
self._store = store
98
105
if branch_filter_expressions is None:
99
106
branch_filter_expressions = []
100
self._branch_filter_expressions = branch_filter_expressions
107
self._branch_filter_expressions = list(branch_filter_expressions)
101
108
if tables is None:
103
110
self._tables = tables
111
if asymmetric_filter_expressions is None:
112
asymmetric_filter_expressions = []
113
self._asymmetric_filter_expressions = list(
114
asymmetric_filter_expressions)
115
if asymmetric_tables is None:
116
asymmetric_tables = {}
117
self._asymmetric_tables = asymmetric_tables
104
118
if exclude_from_search is None:
105
119
exclude_from_search = []
106
120
self._exclude_from_search = exclude_from_search
132
146
return self._store
134
148
def _filterBy(self, expressions, table=None, join=None,
135
exclude_from_search=None):
136
"""Return a subset of this collection, filtered by 'expressions'."""
149
exclude_from_search=None, symmetric=True):
150
"""Return a subset of this collection, filtered by 'expressions'.
152
:param symmetric: If True this filter will apply to both sides of merge
153
proposal lookups and any other lookups that join Branch back onto
137
156
# NOTE: JonathanLange 2009-02-17: We might be able to avoid the need
138
157
# for explicit 'tables' by harnessing Storm's table inference system.
139
158
# See http://paste.ubuntu.com/118711/ for one way to do that.
140
tables = self._tables.copy()
141
159
if table is not None:
143
161
raise InvalidFilter("Cannot specify a table without a join.")
162
if expressions is None:
164
tables = self._tables.copy()
165
asymmetric_tables = self._asymmetric_tables.copy()
167
if table is not None:
169
symmetric_expr = self._branch_filter_expressions + expressions
170
asymmetric_expr = list(self._asymmetric_filter_expressions)
172
if table is not None:
173
asymmetric_tables[table] = join
174
symmetric_expr = list(self._branch_filter_expressions)
175
asymmetric_expr = self._asymmetric_filter_expressions + expressions
145
176
if exclude_from_search is None:
146
177
exclude_from_search = []
147
if expressions is None:
149
178
return self.__class__(
151
self._branch_filter_expressions + expressions,
153
self._exclude_from_search + exclude_from_search)
182
self._exclude_from_search + exclude_from_search,
155
186
def _getBranchIdQuery(self):
156
187
"""Return a Storm 'Select' for the branch IDs in this collection."""
161
192
def _getBranchExpressions(self):
162
193
"""Return the where expressions for this collection."""
163
return (self._branch_filter_expressions
164
+ self._getBranchVisibilityExpression())
194
return (self._branch_filter_expressions +
195
self._asymmetric_filter_expressions +
196
self._getBranchVisibilityExpression())
166
198
def _getBranchVisibilityExpression(self, branch_class=None):
167
199
"""Return the where clauses for visibility."""
202
def _getCandidateBranchesWith(self):
203
"""Return WITH clauses defining candidate branches.
205
These are defined in terms of scope_branches which should be separately
209
With("candidate_branches", SQL("SELECT id from scope_branches"))]
170
211
def getBranches(self, eager_load=False):
171
212
"""See `IBranchCollection`."""
172
tables = [Branch] + self._tables.values()
214
self._tables.values() + self._asymmetric_tables.values())
215
tables = [Branch] + list(all_tables)
173
216
expressions = self._getBranchExpressions()
174
217
resultset = self.store.using(*tables).find(Branch, *expressions)
175
218
if not eager_load:
221
264
def getMergeProposals(self, statuses=None, for_branches=None,
222
265
target_branch=None, merged_revnos=None):
223
266
"""See `IBranchCollection`."""
224
Target = ClassAlias(Branch, "target")
225
tables = [Branch] + self._tables.values() + [
226
Join(BranchMergeProposal, And(
227
Branch.id==BranchMergeProposal.source_branchID,
228
*self._branch_filter_expressions)),
229
Join(Target, Target.id==BranchMergeProposal.target_branchID)
231
expressions = self._getBranchVisibilityExpression()
232
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())
233
286
if for_branches is not None:
234
287
branch_ids = [branch.id for branch in for_branches]
235
288
expressions.append(
531
588
if person is None:
532
589
return AnonymousBranchCollection(
533
590
self._store, self._branch_filter_expressions,
534
self._tables, self._exclude_from_search)
591
self._tables, self._exclude_from_search,
592
self._asymmetric_filter_expressions, self._asymmetric_tables)
535
593
return VisibleBranchCollection(
536
594
person, self._store, self._branch_filter_expressions,
537
self._tables, self._exclude_from_search)
595
self._tables, self._exclude_from_search,
596
self._asymmetric_filter_expressions, self._asymmetric_tables)
539
598
def withBranchType(self, *branch_types):
540
return self._filterBy([Branch.branch_type.is_in(branch_types)])
599
return self._filterBy([Branch.branch_type.is_in(branch_types)],
542
602
def withLifecycleStatus(self, *statuses):
543
603
"""See `IBranchCollection`."""
544
return self._filterBy([Branch.lifecycle_status.is_in(statuses)])
604
return self._filterBy([Branch.lifecycle_status.is_in(statuses)],
546
607
def modifiedSince(self, epoch):
547
608
"""See `IBranchCollection`."""
548
return self._filterBy([Branch.date_last_modified > epoch])
609
return self._filterBy([Branch.date_last_modified > epoch],
550
612
def scannedSince(self, epoch):
551
613
"""See `IBranchCollection`."""
552
return self._filterBy([Branch.last_scanned > epoch])
614
return self._filterBy([Branch.last_scanned > epoch], symmetric=False)
555
617
class AnonymousBranchCollection(GenericBranchCollection):
556
618
"""Branch collection that only shows public branches."""
558
def __init__(self, store=None, branch_filter_expressions=None,
559
tables=None, exclude_from_search=None):
560
super(AnonymousBranchCollection, self).__init__(
562
branch_filter_expressions=list(branch_filter_expressions),
563
tables=tables, exclude_from_search=exclude_from_search)
565
620
def _getBranchVisibilityExpression(self, branch_class=Branch):
566
621
"""Return the where clauses for visibility."""
567
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"))
570
637
class VisibleBranchCollection(GenericBranchCollection):
571
638
"""A branch collection that has special logic for visibility."""
573
640
def __init__(self, user, store=None, branch_filter_expressions=None,
574
tables=None, exclude_from_search=None):
641
tables=None, exclude_from_search=None,
642
asymmetric_filter_expressions=None, asymmetric_tables=None):
575
643
super(VisibleBranchCollection, self).__init__(
576
644
store=store, branch_filter_expressions=branch_filter_expressions,
577
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)
578
648
self._user = user
579
649
self._private_branch_ids = self._getPrivateBranchSubQuery()
581
651
def _filterBy(self, expressions, table=None, join=None,
582
exclude_from_search=None):
583
"""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
584
659
# NOTE: JonathanLange 2009-02-17: We might be able to avoid the need
585
660
# for explicit 'tables' by harnessing Storm's table inference system.
586
661
# See http://paste.ubuntu.com/118711/ for one way to do that.
587
tables = self._tables.copy()
588
662
if table is not None:
590
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
592
679
if exclude_from_search is None:
593
680
exclude_from_search = []
594
if expressions is None:
596
681
return self.__class__(
599
self._branch_filter_expressions + expressions,
601
self._exclude_from_search + exclude_from_search)
686
self._exclude_from_search + exclude_from_search,
603
690
def _getPrivateBranchSubQuery(self):
604
691
"""Return a subquery to get the private branches the user can see.
649
736
branch_class.id.is_in(self._private_branch_ids))
650
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.teamID,
754
TeamParticipation.personID == person.id)._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)"""))
652
766
def visibleByUser(self, person):
653
767
"""See `IBranchCollection`."""
654
768
if person == self._user: