Merge lp:~jtv/launchpad/branchrevision into lp:launchpad/db-devel

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: 9539
Proposed branch: lp:~jtv/launchpad/branchrevision
Merge into: lp:launchpad/db-devel
Diff against target: 1001 lines (+221/-168)
11 files modified
lib/lp/code/doc/branch.txt (+30/-24)
lib/lp/code/doc/revision.txt (+26/-18)
lib/lp/code/interfaces/branch.py (+14/-11)
lib/lp/code/interfaces/branchrevision.py (+0/-1)
lib/lp/code/model/branch.py (+36/-32)
lib/lp/code/model/branchmergeproposal.py (+43/-30)
lib/lp/code/model/branchrevision.py (+30/-17)
lib/lp/code/model/revision.py (+4/-4)
lib/lp/code/model/tests/test_branchjob.py (+14/-15)
lib/lp/codehosting/scanner/tests/test_bzrsync.py (+19/-12)
lib/lp/services/database/prejoin.py (+5/-4)
To merge this branch: bzr merge lp:~jtv/launchpad/branchrevision
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+29791@code.launchpad.net

Commit message

Stormify BranchRevision.

Description of the change

= Stormify BranchRevision =

This branch is part of an effort to eliminate the id field from BranchRevision. The table is too large and ids are not far from running out. (The alternative is to expand the field and make the table even larger). The branch is against db-devel, since this is part of work that will affect the schema. We probably should consider landing it against devel. But all that happens here is to stormify the class and the queries that mention it.

To test, I ran all unit and doc tests for Code (including browser & model unit tests).

There is a bit of pre-existing lint involving tuples like (foo,) not having a space after the comma. I think the existing spelling is appropriate and I'll discuss possible changes to the checker with Curtis.

Jeroen

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (11.4 KiB)

Hi Jeroen,

This is a nice improvement. Comments below.

-Edwin

>=== modified file 'lib/lp/code/interfaces/branchrevision.py'
>--- lib/lp/code/interfaces/branchrevision.py 2009-06-25 04:06:00 +0000
>+++ lib/lp/code/interfaces/branchrevision.py 2010-07-13 13:32:27 +0000
>@@ -21,7 +21,7 @@
> ancestry of a branch. History revisions have an integer sequence, merged
> revisions have sequence set to None.
> """
>-
>+ # XXX JeroenVermeulen 2010-07-12: We're dropping this column.

I assume you are planning on adding a bug for this XXX.

> id = Int(title=_('The database revision ID'))
>
> sequence = Int(
>
>=== modified file 'lib/lp/code/model/branch.py'
>--- lib/lp/code/model/branch.py 2010-07-09 10:22:32 +0000
>+++ lib/lp/code/model/branch.py 2010-07-13 13:32:27 +0000
>@@ -226,11 +226,12 @@
>
> @property
> def revision_history(self):
>- return BranchRevision.select('''
>- BranchRevision.branch = %s AND
>- BranchRevision.sequence IS NOT NULL
>- ''' % sqlvalues(self),
>- prejoins=['revision'], orderBy='-sequence')
>+ # XXX JeroenVermeulen 2010-07-12: Prejoin revision.
>+ result = Store.of(self).find(
>+ BranchRevision,
>+ BranchRevision.branch_id == self.id,
>+ BranchRevision.sequence != None)
>+ return result.order_by(Desc(BranchRevision.sequence))

You can prejoin the revision like this. The Revision will be added
to the cache, and since BranchRevision has the foreign key, it will
retrieve the Revision from the cache by id, even though the list
comprehension appears to throw it away.

> result = Store.of(self).find(
> (BranchRevision, Revision),
> BranchRevision.revisionID == Revision.id,
> BranchRevision.branch_id == self.id,
> BranchRevision.sequence != None)
> result = result.order_by(Desc(BranchRevision.sequence))
> return [branch_revision for branch_revision, branch in result]
>
>
> subscriptions = SQLMultipleJoin(
> 'BranchSubscription', joinColumn='branch', orderBy='id')
>@@ -509,7 +510,7 @@
>
> def latest_revisions(self, quantity=10):
> """See `IBranch`."""
>- return self.revision_history.limit(quantity)
>+ return self.revision_history.config(limit=quantity)

Of course, if you use the prejoin above, you will need access to
the storm query before stripping out the Revision.

> def getMainlineBranchRevisions(self, start_date, end_date=None,
> oldest_first=False):
>@@ -532,14 +533,13 @@
>
> def getRevisionsSince(self, timestamp):
> """See `IBranch`."""
>- return BranchRevision.select(
>- 'Revision.id=BranchRevision.revision AND '
>- 'BranchRevision.branch = %d AND '
>- 'BranchRevision.sequence IS NOT NULL AND '
>- 'Revision.revision_date > %s' %
>- (self.id, quote(timestamp)),
>- orderBy='-sequence',
>- clauseTables=['Revision'])
>+ result = Store.of(self).using(Revision).find(
>+ BranchRevision,
>+ Revision == BranchRevision.revision,
>...

review: Approve (code)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (7.3 KiB)

Thanks for a particularly thoughtful and helpful review.

> >=== modified file 'lib/lp/code/interfaces/branchrevision.py'
> >--- lib/lp/code/interfaces/branchrevision.py 2009-06-25 04:06:00 +0000
> >+++ lib/lp/code/interfaces/branchrevision.py 2010-07-13 13:32:27 +0000
> >@@ -21,7 +21,7 @@
> > ancestry of a branch. History revisions have an integer sequence, merged
> > revisions have sequence set to None.
> > """
> >-
> >+ # XXX JeroenVermeulen 2010-07-12: We're dropping this column.
>
>
> I assume you are planning on adding a bug for this XXX.

This was really just a transient development note; I've dropped it so that we don't leave unnecessary cruft lying around.

> >=== modified file 'lib/lp/code/model/branch.py'
> >--- lib/lp/code/model/branch.py 2010-07-09 10:22:32 +0000
> >+++ lib/lp/code/model/branch.py 2010-07-13 13:32:27 +0000

> >+ result = Store.of(self).find(
> >+ BranchRevision,
> >+ BranchRevision.branch_id == self.id,
> >+ BranchRevision.sequence != None)
> >+ return result.order_by(Desc(BranchRevision.sequence))
>
>
> You can prejoin the revision like this. The Revision will be added
> to the cache, and since BranchRevision has the foreign key, it will
> retrieve the Revision from the cache by id, even though the list
> comprehension appears to throw it away.

I used Stuart's prejoin helper from lp.services.database. I cleaned that up a bit as well, and corrected the documentation.

> >@@ -509,7 +510,7 @@
> >
> > def latest_revisions(self, quantity=10):
> > """See `IBranch`."""
> >- return self.revision_history.limit(quantity)
> >+ return self.revision_history.config(limit=quantity)
>
>
> Of course, if you use the prejoin above, you will need access to
> the storm query before stripping out the Revision.

Thankfully the prejoin helper takes care of that!

> >@@ -532,14 +533,13 @@
> >
> > def getRevisionsSince(self, timestamp):
> > """See `IBranch`."""
> >- return BranchRevision.select(
> >- 'Revision.id=BranchRevision.revision AND '
> >- 'BranchRevision.branch = %d AND '
> >- 'BranchRevision.sequence IS NOT NULL AND '
> >- 'Revision.revision_date > %s' %
> >- (self.id, quote(timestamp)),
> >- orderBy='-sequence',
> >- clauseTables=['Revision'])
> >+ result = Store.of(self).using(Revision).find(
> >+ BranchRevision,
> >+ Revision == BranchRevision.revision,
> >+ BranchRevision.branch == self,
> >+ BranchRevision.sequence != None,
> >+ Revision.revision_date > timestamp)
> >+ return result.order_by(Desc(BranchRevision.sequence))
>
> A prejoin would be good here, too.

Done.

> >@@ -860,8 +860,8 @@
> >
> > def getScannerData(self):
> > """See `IBranch`."""
> >- cur = cursor()
> >- cur.execute("""
> >+# XXX JeroenVermeulen 2010-07-12: We're dropping BranchRevision.id.
>
>
> This XXX needs that bug number and some indentation.

Removed as above.

> >+ rows = Store.of(self).execute("""
>
>
> Storm queries can retriev...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/doc/branch.txt'
2--- lib/lp/code/doc/branch.txt 2010-05-27 02:09:13 +0000
3+++ lib/lp/code/doc/branch.txt 2010-07-14 16:19:43 +0000
4@@ -158,10 +158,10 @@
5
6 == Determining the recently changed, registered and imported branches ==
7
8-The IBranchSet methods getRecentlyChangedBranches, getRecentlyImportedBranches,
9-and getRecentlyRegisteredBranches are used to give summary information that
10-is to be displayed on the code.launchpad.net page to entice the
11-user to click through.
12+The IBranchSet methods getRecentlyChangedBranches,
13+getRecentlyImportedBranches, and getRecentlyRegisteredBranches are used to
14+give summary information that is to be displayed on the code.launchpad.net
15+page to entice the user to click through.
16
17 Changed branches are branches that are owned by real people or groups (as
18 opposed to vcs-imports), and have recently had new revisions detected by
19@@ -315,8 +315,8 @@
20 True
21 >>> subscription.branch == branch and subscription.person == subscriber
22 True
23- >>> subscription.notification_level == BranchSubscriptionNotificationLevel.FULL
24- True
25+ >>> print subscription.notification_level.name
26+ FULL
27 >>> subscription.max_diff_lines == BranchSubscriptionDiffSize.FIVEKLINES
28 True
29 >>> subscription.review_level == CodeReviewNotificationLevel.FULL
30@@ -327,7 +327,7 @@
31 True
32 >>> from canonical.launchpad.webapp import canonical_url
33 >>> print canonical_url(subscription)
34- http://code.launchpad.dev/~user/product/subscribed/+subscription/subscriber
35+ http://code...dev/~user/product/subscribed/+subscription/subscriber
36
37 The settings for a subscription can be changed by re-subscribing.
38
39@@ -399,8 +399,8 @@
40
41 Branch.revision_history gives the sequence of revisions in this branch's
42 history, latest revisions first. All revision history items must implement the
43-IBranchRevision interface. The Branch.revision_count attribute gives the length
44-of the revision_history attribute but without building the list.
45+IBranchRevision interface. The Branch.revision_count attribute gives the
46+length of the revision_history attribute but without building the list.
47
48 >>> from lp.code.interfaces.branchrevision import IBranchRevision
49 >>> junk.revision_count
50@@ -427,11 +427,11 @@
51 True
52
53 Branch.getRevisionsSince gives all the BranchRevisions for revisions committed
54-since a given timestamp. It may give surprising results if some committers had a
55-skewed clock.
56+since a given timestamp. It may give surprising results if some committers had
57+a skewed clock.
58
59 >>> from datetime import datetime
60- >>> timestamp = datetime(2005, 10, 31, 12, 00, 00)
61+ >>> timestamp = datetime(2005, 10, 31, 12, 00, 00, 0, pytz.UTC)
62 >>> two_latest = list(junk.revision_history)[:2]
63 >>> list(junk.getRevisionsSince(timestamp)) == two_latest
64 True
65@@ -440,10 +440,11 @@
66 == Ancestry of Revision ==
67
68 The revision-history of a given branch, is only one possible ancestry path in
69-the ancestry graph. It is also possible to examine the ancestry graph directly.
70+the ancestry graph. It is also possible to examine the ancestry graph
71+directly.
72
73-A Bazaar branch may contains references (by revision_id) to revisions for which
74-no data is available. Such revisions are called "ghosts".
75+A Bazaar branch may contains references (by revision_id) to revisions for
76+which no data is available. Such revisions are called "ghosts".
77
78 Initial commits (after a "bzr init") revisions have no parent.
79
80@@ -452,8 +453,8 @@
81 >>> initial.parent_ids
82 []
83
84-Normal commits (as opposed to merges) have exactly one parent. The first parent
85-of a revision is always the revision that was current when committing.
86+Normal commits (as opposed to merges) have exactly one parent. The first
87+parent of a revision is always the revision that was current when committing.
88
89 >>> commit = history[-2].revision
90 >>> [type(a) for a in commit.parent_ids] == [unicode]
91@@ -492,8 +493,8 @@
92 is identified by the branch attribute "last_scanned_id". This is the textual
93 revision_id for the bzr revision. The reason that it is a text id rather than
94 an integer foreign key is so it can easily be compared to the
95-"last_mirrored_id". The "last_mirrored_id" is set by the branch puller, and is
96-used to identify when a scan is needed for a branch.
97+"last_mirrored_id". The "last_mirrored_id" is set by the branch puller, and
98+is used to identify when a scan is needed for a branch.
99
100 >>> branch = branch_lookup.get(1)
101 >>> branch.last_scanned_id
102@@ -504,7 +505,8 @@
103 >>> branch.getTipRevision() is None
104 True
105
106- >>> branch.last_scanned_id = 'test@canonical.com-20051031165248-6f1bb97973c2b4f4'
107+ >>> branch.last_scanned_id = (
108+ ... 'test@canonical.com-20051031165248-6f1bb97973c2b4f4')
109 >>> rev = branch.getTipRevision()
110 >>> print rev.date_created
111 2005-10-31 17:21:47.381770+00:00
112@@ -560,7 +562,8 @@
113
114 == Deleting branches ==
115
116-If a user creates a branch in error, they should be able to remove that branch.
117+If a user creates a branch in error, they should be able to remove that
118+branch.
119
120 A branch can be deleted trivially if it is not associated with any bugs or
121 blueprints, has no subscribers, and hasn't been associated with any product
122@@ -585,9 +588,12 @@
123 >>> cur = cursor()
124 >>> references = list(postgresql.listReferences(cur, 'branch', 'id'))
125
126- >>> for name in sorted([
127- ... '%s.%s' % (src_tab, src_col) for
128- ... src_tab, src_col, ref_tab, ref_col, updact, delact in references]):
129+ >>> listing = sorted([
130+ ... '%s.%s' % (src_tab, src_col)
131+ ... for src_tab, src_col, ref_tab, ref_col, updact, delact
132+ ... in references
133+ ... ])
134+ >>> for name in listing:
135 ... print name
136 branch.stacked_on
137 branchjob.branch
138
139=== modified file 'lib/lp/code/doc/revision.txt'
140--- lib/lp/code/doc/revision.txt 2009-06-16 03:31:05 +0000
141+++ lib/lp/code/doc/revision.txt 2010-07-14 16:19:43 +0000
142@@ -1,11 +1,12 @@
143 = Bazaar Revisions =
144
145 Branches are collection of revisions, and a revision can exist independently
146-from any branch. Revisions are created automatically by scanning branches, they
147-have no creation interface and Launchpad cannot create or modify them.
148+from any branch. Revisions are created automatically by scanning branches,
149+they have no creation interface and Launchpad cannot create or modify them.
150
151 == Interfaces ==
152
153+ >>> from canonical.launchpad.interfaces.lpstorm import IStore
154 >>> from canonical.launchpad.webapp.testing import verifyObject
155 >>> from lp.code.interfaces.revision import (
156 ... IRevision, IRevisionAuthor, IRevisionParent, IRevisionSet)
157@@ -21,14 +22,16 @@
158 True
159 >>> verifyObject(IRevisionSet, RevisionSet())
160 True
161- >>> verifyObject(IBranchRevision, BranchRevision.get(1))
162+ >>> verifyObject(
163+ ... IBranchRevision,
164+ ... IStore(BranchRevision).find(BranchRevision).any())
165 True
166
167 == Creating revisions ==
168
169 The creator of a revision is identified by a RevisionAuthor. A RevisionAuthor
170-is not a person because that is only an informational attribute, and even if we
171-trust it, there's really no simple way to map that reliably to persons.
172+is not a person because that is only an informational attribute, and even if
173+we trust it, there's really no simple way to map that reliably to persons.
174
175 >>> from lp.code.model.revision import RevisionAuthor
176 >>> author = RevisionAuthor(name='ddaa@localhost')
177@@ -55,7 +58,8 @@
178 >>> date = datetime(2005, 3, 8, 12, 0, tzinfo=UTC)
179 >>> from lp.code.model.revision import Revision
180 >>> revision_1 = Revision(log_body=log_body_1,
181- ... revision_author=author, revision_id=revision_id_1, revision_date=date)
182+ ... revision_author=author, revision_id=revision_id_1,
183+ ... revision_date=date)
184
185 == Parents ==
186
187@@ -67,7 +71,8 @@
188 we can represent revisions whose at least one parent is a ghost revision.
189
190 >>> revision_2 = Revision(log_body=log_body_2,
191- ... revision_author=author, revision_id=revision_id_2, revision_date=date)
192+ ... revision_author=author, revision_id=revision_id_2,
193+ ... revision_date=date)
194
195 >>> from lp.code.model.revision import RevisionParent
196 >>> rev2_parent = RevisionParent(sequence=0, revision=revision_2,
197@@ -86,8 +91,10 @@
198
199 BranchRevision rows are created using `Branch.createBranchRevision`.
200
201- >>> rev_no_1 = branch.createBranchRevision(sequence=1, revision=revision_1)
202- >>> rev_no_2 = branch.createBranchRevision(sequence=2, revision=revision_2)
203+ >>> rev_no_1 = branch.createBranchRevision(
204+ ... sequence=1, revision=revision_1)
205+ >>> rev_no_2 = branch.createBranchRevision(
206+ ... sequence=2, revision=revision_2)
207 >>> rev_no_1.branch == rev_no_2.branch == branch
208 True
209
210@@ -96,13 +103,14 @@
211 >>> branch = getUtility(IBranchLookup).getByUniqueName(
212 ... '~name12/+junk/junk.contrib')
213
214-The full ancestry of a branch is recorded. That includes the history commits on
215-this branch, but also revisions that were merged into this branch. Such merged
216-revisions are associated to the branch using BranchRevision whose sequence
217-attribute is None.
218+The full ancestry of a branch is recorded. That includes the history commits
219+on this branch, but also revisions that were merged into this branch. Such
220+merged revisions are associated to the branch using BranchRevision whose
221+sequence attribute is None.
222
223 >>> from lp.code.model.branchrevision import BranchRevision
224- >>> ancestry = BranchRevision.selectBy(branch=branch)
225+ >>> ancestry = IStore(BranchRevision).find(
226+ ... BranchRevision, BranchRevision.branch == branch)
227 >>> for branch_revision in sorted(ancestry,
228 ... key=lambda r:(r.sequence, r.revision.id), reverse=True):
229 ... print branch_revision.sequence, branch_revision.revision.id
230@@ -116,16 +124,16 @@
231 None 6
232
233 If you need to operate on the ancestry of a branch, you should write a focused
234-query to avoid creating the tens of thousands of objects necessary to represent
235-the ancestry of a large branch.
236+query to avoid creating the tens of thousands of objects necessary to
237+represent the ancestry of a large branch.
238
239 In particular, IBranch.getScannerData efficiently retrieves the BranchRevision
240 data needed by the branch-scanner script.
241
242 >>> ancestry, history, mapping = branch.getScannerData()
243
244-The first return value is a set of revision_id strings for the full ancestry of
245-the branch.
246+The first return value is a set of revision_id strings for the full ancestry
247+of the branch.
248
249 >>> for revision_id in sorted(ancestry):
250 ... print revision_id
251
252=== modified file 'lib/lp/code/interfaces/branch.py'
253--- lib/lp/code/interfaces/branch.py 2010-07-09 10:22:32 +0000
254+++ lib/lp/code/interfaces/branch.py 2010-07-14 16:19:43 +0000
255@@ -463,9 +463,9 @@
256 is set, the branch gets moved into the junk namespace of the branch
257 owner.
258
259- :raise: `BranchTargetError` if both project and source_package are set,
260- or if either the project or source_package fail to be adapted to an
261- IBranchTarget.
262+ :raise: `BranchTargetError` if both project and source_package are
263+ set, or if either the project or source_package fail to be adapted
264+ to an `IBranchTarget`.
265 """
266
267 reviewer = exported(
268@@ -668,12 +668,14 @@
269
270 # Joins
271 revision_history = Attribute(
272- """The sequence of BranchRevision for the mainline of that branch.
273+ """The sequence of revisions for the mainline of this branch.
274
275 They are ordered with the most recent revision first, and the list
276 only contains those in the "leftmost tree", or in other words
277 the revisions that match the revision history from bzrlib for this
278 branch.
279+
280+ The revisions are listed as tuples of (`BranchRevision`, `Revision`).
281 """)
282 subscriptions = exported(
283 CollectionField(
284@@ -703,7 +705,7 @@
285 def destroySelfBreakReferences():
286 """Delete the specified branch.
287
288- BranchRevisions associated with this branch will also be deleted as
289+ BranchRevisions associated with this branch will also be deleted as
290 well as any items with mandatory references.
291 """
292
293@@ -766,8 +768,7 @@
294 title=_('Commit message'),
295 description=_('Message to use when committing this merge.')),
296 reviewers=List(value_type=Reference(schema=IPerson)),
297- review_types=List(value_type=TextLine())
298- )
299+ review_types=List(value_type=TextLine()))
300 # target_branch and prerequisite_branch are actually IBranch, patched in
301 # _schema_circular_imports.
302 @call_with(registrant=REQUEST_USER)
303@@ -782,7 +783,8 @@
304 Both the target_branch and the prerequisite_branch, if it is there,
305 must be branches with the same target as the source branch.
306
307- Personal branches (a.k.a. junk branches) cannot specify landing targets.
308+ Personal branches (a.k.a. junk branches) cannot specify landing
309+ targets.
310 """
311
312 def addLandingTarget(registrant, target_branch, prerequisite_branch=None,
313@@ -794,7 +796,8 @@
314 Both the target_branch and the prerequisite_branch, if it is there,
315 must be branches with the same target as the source branch.
316
317- Personal branches (a.k.a. junk branches) cannot specify landing targets.
318+ Personal branches (a.k.a. junk branches) cannot specify landing
319+ targets.
320
321 :param registrant: The person who is adding the landing target.
322 :param target_branch: Must be another branch, and different to self.
323@@ -949,8 +952,8 @@
324 project is accessible using:
325 lp:fooix - the linked object is the product fooix
326 lp:fooix/trunk - the linked object is the trunk series of fooix
327- lp:~owner/fooix/name - the unique name of the branch where the linked
328- object is the branch itself.
329+ lp:~owner/fooix/name - the unique name of the branch where the
330+ linked object is the branch itself.
331 """
332
333 # subscription-related methods
334
335=== modified file 'lib/lp/code/interfaces/branchrevision.py'
336--- lib/lp/code/interfaces/branchrevision.py 2009-06-25 04:06:00 +0000
337+++ lib/lp/code/interfaces/branchrevision.py 2010-07-14 16:19:43 +0000
338@@ -21,7 +21,6 @@
339 ancestry of a branch. History revisions have an integer sequence, merged
340 revisions have sequence set to None.
341 """
342-
343 id = Int(title=_('The database revision ID'))
344
345 sequence = Int(
346
347=== modified file 'lib/lp/code/model/branch.py'
348--- lib/lp/code/model/branch.py 2010-07-09 10:22:32 +0000
349+++ lib/lp/code/model/branch.py 2010-07-14 16:19:43 +0000
350@@ -34,7 +34,7 @@
351 from canonical.config import config
352 from canonical.database.constants import DEFAULT, UTC_NOW
353 from canonical.database.sqlbase import (
354- cursor, quote, SQLBase, sqlvalues)
355+ SQLBase, sqlvalues)
356 from canonical.database.datetimecol import UtcDateTimeCol
357 from canonical.database.enumcol import EnumCol
358
359@@ -79,6 +79,7 @@
360 from lp.codehosting.bzrutils import safe_open
361 from lp.registry.interfaces.person import (
362 validate_person_not_private_membership, validate_public_person)
363+from lp.services.database.prejoin import prejoin
364 from lp.services.job.interfaces.job import JobStatus
365 from lp.services.mail.notificationrecipientset import (
366 NotificationRecipientSet)
367@@ -226,11 +227,13 @@
368
369 @property
370 def revision_history(self):
371- return BranchRevision.select('''
372- BranchRevision.branch = %s AND
373- BranchRevision.sequence IS NOT NULL
374- ''' % sqlvalues(self),
375- prejoins=['revision'], orderBy='-sequence')
376+ result = Store.of(self).find(
377+ (BranchRevision, Revision),
378+ BranchRevision.branch_id == self.id,
379+ Revision.id == BranchRevision.revision_id,
380+ BranchRevision.sequence != None)
381+ result = result.order_by(Desc(BranchRevision.sequence))
382+ return prejoin(result, return_slice=slice(0, 1))
383
384 subscriptions = SQLMultipleJoin(
385 'BranchSubscription', joinColumn='branch', orderBy='id')
386@@ -450,7 +453,7 @@
387 @property
388 def code_is_browseable(self):
389 """See `IBranch`."""
390- return (self.revision_count > 0 or self.last_mirrored != None)
391+ return (self.revision_count > 0 or self.last_mirrored != None)
392
393 def codebrowse_url(self, *extras):
394 """See `IBranch`."""
395@@ -509,7 +512,7 @@
396
397 def latest_revisions(self, quantity=10):
398 """See `IBranch`."""
399- return self.revision_history.limit(quantity)
400+ return self.revision_history.config(limit=quantity)
401
402 def getMainlineBranchRevisions(self, start_date, end_date=None,
403 oldest_first=False):
404@@ -532,14 +535,15 @@
405
406 def getRevisionsSince(self, timestamp):
407 """See `IBranch`."""
408- return BranchRevision.select(
409- 'Revision.id=BranchRevision.revision AND '
410- 'BranchRevision.branch = %d AND '
411- 'BranchRevision.sequence IS NOT NULL AND '
412- 'Revision.revision_date > %s' %
413- (self.id, quote(timestamp)),
414- orderBy='-sequence',
415- clauseTables=['Revision'])
416+ result = Store.of(self).find(
417+ (BranchRevision, Revision),
418+ Revision.id == BranchRevision.revision_id,
419+ BranchRevision.branch == self,
420+ BranchRevision.sequence != None,
421+ Revision.revision_date > timestamp)
422+ result = result.order_by(Desc(BranchRevision.sequence))
423+ # Return BranchRevision but prejoin Revision as well.
424+ return prejoin(result, slice(0, 1))
425
426 def canBeDeleted(self):
427 """See `IBranch`."""
428@@ -860,19 +864,17 @@
429
430 def getScannerData(self):
431 """See `IBranch`."""
432- cur = cursor()
433- cur.execute("""
434- SELECT BranchRevision.id, BranchRevision.sequence,
435- Revision.revision_id
436- FROM Revision, BranchRevision
437- WHERE Revision.id = BranchRevision.revision
438- AND BranchRevision.branch = %s
439- ORDER BY BranchRevision.sequence
440- """ % sqlvalues(self))
441+ columns = (
442+ BranchRevision.id, BranchRevision.sequence, Revision.revision_id)
443+ rows = Store.of(self).using(Revision, BranchRevision).find(
444+ columns,
445+ Revision.id == BranchRevision.revision_id,
446+ BranchRevision.branch_id == self.id)
447+ rows = rows.order_by(BranchRevision.sequence)
448 ancestry = set()
449 history = []
450 branch_revision_map = {}
451- for branch_revision_id, sequence, revision_id in cur.fetchall():
452+ for branch_revision_id, sequence, revision_id in rows:
453 ancestry.add(revision_id)
454 branch_revision_map[revision_id] = branch_revision_id
455 if sequence is not None:
456@@ -890,19 +892,19 @@
457 prefix = config.launchpad.bzr_imports_root_url
458 return urlappend(prefix, '%08x' % self.id)
459 else:
460- raise AssertionError("No pull URL for %r" % (self,))
461+ raise AssertionError("No pull URL for %r" % (self, ))
462
463 def requestMirror(self):
464 """See `IBranch`."""
465 if self.branch_type == BranchType.REMOTE:
466 raise BranchTypeError(self.unique_name)
467 from canonical.launchpad.interfaces import IStore
468- IStore(self).find(
469+ branch = IStore(self).find(
470 Branch,
471 Branch.id == self.id,
472 Or(Branch.next_mirror_time > UTC_NOW,
473- Branch.next_mirror_time == None)
474- ).set(next_mirror_time=UTC_NOW)
475+ Branch.next_mirror_time == None))
476+ branch.set(next_mirror_time=UTC_NOW)
477 self.next_mirror_time = AutoReload
478 return self.next_mirror_time
479
480@@ -997,11 +999,12 @@
481
482 def commitsForDays(self, since):
483 """See `IBranch`."""
484+
485 class DateTrunc(NamedFunc):
486 name = "date_trunc"
487 results = Store.of(self).find(
488 (DateTrunc('day', Revision.revision_date), Count(Revision.id)),
489- Revision.id == BranchRevision.revisionID,
490+ Revision.id == BranchRevision.revision_id,
491 Revision.revision_date > since,
492 BranchRevision.branch == self)
493 results = results.group_by(
494@@ -1080,6 +1083,7 @@
495 def __init__(self, affected_object, rationale):
496 self.affected_object = ProxyFactory(affected_object)
497 self.rationale = rationale
498+
499 def __call__(self):
500 """Perform the deletion operation."""
501 raise NotImplementedError(DeletionOperation.__call__)
502@@ -1161,7 +1165,7 @@
503
504 def __init__(self, code_import):
505 DeletionOperation.__init__(
506- self, code_import, _( 'This is the import data for this branch.'))
507+ self, code_import, _('This is the import data for this branch.'))
508
509 def __call__(self):
510 from lp.code.model.codeimport import CodeImportSet
511
512=== modified file 'lib/lp/code/model/branchmergeproposal.py'
513--- lib/lp/code/model/branchmergeproposal.py 2010-05-07 04:53:47 +0000
514+++ lib/lp/code/model/branchmergeproposal.py 2010-07-14 16:19:43 +0000
515@@ -1,4 +1,4 @@
516-# Copyright 2009 Canonical Ltd. This software is licensed under the
517+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
518 # GNU Affero General Public License version 3 (see the file LICENSE).
519
520 # pylint: disable-msg=E0611,W0212,F0401
521@@ -13,13 +13,13 @@
522 ]
523
524 from email.Utils import make_msgid
525-from storm.expr import And, Or, Select
526+from storm.expr import And, Desc, Join, LeftJoin, Or, Select
527+from storm.info import ClassAlias
528 from storm.store import Store
529 from zope.component import getUtility
530 from zope.event import notify
531 from zope.interface import implements
532
533-from storm.expr import Join, LeftJoin
534 from storm.locals import Int, Reference
535 from sqlobject import ForeignKey, IntCol, StringCol, SQLMultipleJoin
536
537@@ -74,9 +74,17 @@
538 if dupes.count() > 0:
539 return False
540
541- [wip, needs_review, code_approved, rejected,
542- merged, merge_failed, queued, superseded
543- ] = BranchMergeProposalStatus.items
544+ [
545+ wip,
546+ needs_review,
547+ code_approved,
548+ rejected,
549+ merged,
550+ merge_failed,
551+ queued,
552+ superseded,
553+ ] = BranchMergeProposalStatus.items
554+
555 # Transitioning to code approved, rejected, failed or queued from
556 # work in progress, needs review or merge failed needs the
557 # user to be a valid reviewer, other states are fine.
558@@ -88,13 +96,13 @@
559 return False
560 # Non-reviewers can toggle within the reviewed ok states
561 # (approved/queued/failed): they can dequeue something they spot an
562- # environmental issue with (queued or failed to approved). Retry things
563- # that had an environmental issue (failed or approved to queued) and note
564- # things as failing (approved and queued to failed).
565+ # environmental issue with (queued or failed to approved). Retry
566+ # things that had an environmental issue (failed or approved to
567+ # queued) and note things as failing (approved and queued to failed).
568 # This is perhaps more generous than needed, but its not clearly wrong
569- # - a key concern is to prevent non reviewers putting things in the
570- # queue that haven't been oked (and thus moved to approved or one of the
571- # workflow states that approved leads to).
572+ # - a key concern is to prevent non reviewers putting things in the
573+ # queue that haven't been approved (and thus moved to approved or one
574+ # of the workflow states that approved leads to).
575 elif (next_state in reviewed_ok_states and
576 from_state not in reviewed_ok_states):
577 return False
578@@ -154,14 +162,14 @@
579 from lp.code.model.branchmergeproposaljob import (
580 BranchMergeProposalJob, BranchMergeProposalJobFactory,
581 BranchMergeProposalJobType)
582- job = Store.of(self).find(
583+ jobs = Store.of(self).find(
584 BranchMergeProposalJob,
585 BranchMergeProposalJob.branch_merge_proposal == self,
586 BranchMergeProposalJob.job_type ==
587 BranchMergeProposalJobType.UPDATE_PREVIEW_DIFF,
588 BranchMergeProposalJob.job == Job.id,
589- Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING])
590- ).order_by(Job.scheduled_start, Job.date_created).first()
591+ Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING]))
592+ job = jobs.order_by(Job.scheduled_start, Job.date_created).first()
593 if job is not None:
594 return BranchMergeProposalJobFactory.create(job)
595 else:
596@@ -486,8 +494,10 @@
597 self.queue_position = None
598
599 if merged_revno is not None:
600- branch_revision = BranchRevision.selectOneBy(
601- branch=self.target_branch, sequence=merged_revno)
602+ branch_revision = Store.of(self).find(
603+ BranchRevision,
604+ BranchRevision.branch == self.target_branch,
605+ BranchRevision.sequence == merged_revno).one()
606 if branch_revision is not None:
607 date_merged = branch_revision.revision.revision_date
608
609@@ -579,14 +589,20 @@
610
611 def getUnlandedSourceBranchRevisions(self):
612 """See `IBranchMergeProposal`."""
613- return BranchRevision.select('''
614- BranchRevision.branch = %s AND
615- BranchRevision.sequence IS NOT NULL AND
616- BranchRevision.revision NOT IN (
617- SELECT revision FROM BranchRevision
618- WHERE branch = %s)
619- ''' % sqlvalues(self.source_branch, self.target_branch),
620- prejoins=['revision'], orderBy='-sequence', limit=10)
621+ store = Store.of(self)
622+ SourceRevision = ClassAlias(BranchRevision)
623+ TargetRevision = ClassAlias(BranchRevision)
624+ target_join = LeftJoin(
625+ TargetRevision, And(
626+ TargetRevision.revision_id == SourceRevision.revision_id,
627+ TargetRevision.branch_id == self.target_branch.id))
628+ origin = [SourceRevision, target_join]
629+ result = store.using(*origin).find(
630+ SourceRevision,
631+ SourceRevision.branch_id == self.source_branch.id,
632+ SourceRevision.sequence != None,
633+ TargetRevision.id == None)
634+ return result.order_by(Desc(SourceRevision.sequence)).config(limit=10)
635
636 def createComment(self, owner, subject, content=None, vote=None,
637 review_type=None, parent=None, _date_created=DEFAULT,
638@@ -649,9 +665,8 @@
639 CodeReviewVoteReference,
640 CodeReviewVoteReference.branch_merge_proposal == self,
641 CodeReviewVoteReference.review_type == review_type,
642- CodeReviewVoteReference.comment == None
643- ).order_by(CodeReviewVoteReference.date_created)
644- for ref in refs:
645+ CodeReviewVoteReference.comment == None)
646+ for ref in refs.order_by(CodeReviewVoteReference.date_created):
647 if user.inTeam(ref.reviewer):
648 return ref
649 return None
650@@ -849,5 +864,3 @@
651 BranchMergeProposal.target_branch = %s AND
652 BranchMergeProposal.queue_status NOT IN %s
653 """ % sqlvalues(source_branch, target_branch, FINAL_STATES))
654-
655-
656
657=== modified file 'lib/lp/code/model/branchrevision.py'
658--- lib/lp/code/model/branchrevision.py 2009-06-25 04:06:00 +0000
659+++ lib/lp/code/model/branchrevision.py 2010-07-14 16:19:43 +0000
660@@ -1,4 +1,4 @@
661-# Copyright 2009 Canonical Ltd. This software is licensed under the
662+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
663 # GNU Affero General Public License version 3 (see the file LICENSE).
664
665 # pylint: disable-msg=E0611,W0212
666@@ -8,30 +8,43 @@
667
668 from zope.interface import implements
669
670-from sqlobject import ForeignKey, IntCol
671-
672-from canonical.database.sqlbase import SQLBase
673-from lp.code.interfaces.branchrevision import IBranchRevision, IBranchRevisionSet
674-class BranchRevision(SQLBase):
675- """See IBranchRevision."""
676+from storm.locals import (Int, Reference, Storm)
677+
678+from canonical.launchpad.interfaces.lpstorm import IMasterStore
679+
680+from lp.code.interfaces.branchrevision import (
681+ IBranchRevision, IBranchRevisionSet)
682+
683+
684+class BranchRevision(Storm):
685+ """See `IBranchRevision`."""
686+ __storm_table__ = 'BranchRevision'
687+
688+ id = Int(primary=True)
689
690 implements(IBranchRevision)
691
692- _table = 'BranchRevision'
693-
694- branch = ForeignKey(
695- dbName='branch', foreignKey='Branch', notNull=True)
696-
697- sequence = IntCol()
698- revision = ForeignKey(
699- dbName='revision', foreignKey='Revision', notNull=True)
700+ branch_id = Int(name='branch', allow_none=False)
701+ branch = Reference(branch_id, 'Branch.id')
702+
703+ revision_id = Int(name='revision', allow_none=False)
704+ revision = Reference(revision_id, 'Revision.id')
705+
706+ sequence = Int(name='sequence', allow_none=True)
707+
708+ def __init__(self, branch, revision, sequence=None):
709+ self.branch = branch
710+ self.revision = revision
711+ self.sequence = sequence
712
713
714 class BranchRevisionSet:
715- """See IBranchRevisionSet."""
716+ """See `IBranchRevisionSet`."""
717
718 implements(IBranchRevisionSet)
719
720 def delete(self, branch_revision_id):
721 """See `IBranchRevisionSet`."""
722- BranchRevision.delete(branch_revision_id)
723+ match = IMasterStore(BranchRevision).find(
724+ BranchRevision, BranchRevision.id == branch_revision_id)
725+ match.remove()
726
727=== modified file 'lib/lp/code/model/revision.py'
728--- lib/lp/code/model/revision.py 2010-04-16 15:06:55 +0000
729+++ lib/lp/code/model/revision.py 2010-07-14 16:19:43 +0000
730@@ -111,8 +111,8 @@
731 store = Store.of(self)
732
733 query = And(
734- self.id == BranchRevision.revisionID,
735- BranchRevision.branchID == Branch.id)
736+ self.id == BranchRevision.revision_id,
737+ BranchRevision.branch_id == Branch.id)
738 if not allow_private:
739 query = And(query, Not(Branch.private))
740 if not allow_junk:
741@@ -123,7 +123,7 @@
742 Or(
743 (Branch.product != None),
744 And(
745- Branch.sourcepackagename != None,
746+ Branch.sourcepackagename != None,
747 Branch.distroseries != None)))
748 result_set = store.find(Branch, query)
749 if self.revision_author.person is None:
750@@ -374,7 +374,7 @@
751 BranchRevision.branch == Branch.id,
752 Branch.product == product,
753 Branch.lifecycle_status.is_in(DEFAULT_BRANCH_STATUS_IN_LISTING),
754- BranchRevision.revisionID >= revision_subselect)
755+ BranchRevision.revision_id >= revision_subselect)
756 result_set.config(distinct=True)
757 return result_set.order_by(Desc(Revision.revision_date))
758
759
760=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
761--- lib/lp/code/model/tests/test_branchjob.py 2010-06-11 03:52:02 +0000
762+++ lib/lp/code/model/tests/test_branchjob.py 2010-07-14 16:19:43 +0000
763@@ -26,6 +26,7 @@
764
765 from canonical.config import config
766 from canonical.database.constants import UTC_NOW
767+from canonical.launchpad.interfaces.lpstorm import IMasterStore
768 from canonical.launchpad.webapp import canonical_url
769 from canonical.launchpad.webapp.testing import verifyObject
770 from lp.translations.interfaces.translations import (
771@@ -113,7 +114,7 @@
772 self.useBzrBranches(direct_database=True)
773
774 tree_location = tempfile.mkdtemp()
775- self.addCleanup(lambda: shutil.rmtree(tree_location))
776+ self.addCleanup(lambda: shutil.rmtree(tree_location))
777
778 branch, tree = self.create_branch_and_tree(
779 tree_location=tree_location)
780@@ -331,7 +332,7 @@
781 job = RevisionMailJob.create(
782 branch, 0, 'from@example.com', 'hello', False, 'subject')
783 job.run()
784- (mail,) = pop_notifications()
785+ (mail, ) = pop_notifications()
786 self.assertEqual('0', mail['X-Launchpad-Branch-Revision-Number'])
787 self.assertEqual('from@example.com', mail['from'])
788 self.assertEqual('subject', mail['subject'])
789@@ -344,7 +345,7 @@
790 'To unsubscribe from this branch go to'
791 ' %(url)s/+edit-subscription\n' % {
792 'url': canonical_url(branch),
793- 'identity': branch.bzr_identity
794+ 'identity': branch.bzr_identity,
795 },
796 mail.get_payload(decode=True))
797
798@@ -454,7 +455,9 @@
799 except bzr_errors.NoSuchRevision:
800 revno = None
801 if existing is not None:
802- BranchRevision.delete(existing.id)
803+ branchrevision = IMasterStore(branch).find(
804+ BranchRevision, BranchRevision.id == existing.id)
805+ branchrevision.remove()
806 branch.createBranchRevision(revno, revision)
807
808 def create3CommitsBranch(self):
809@@ -913,7 +916,7 @@
810 self.series = None
811
812 def _makeBranchWithTreeAndFile(self, file_name, file_content = None):
813- return self._makeBranchWithTreeAndFiles(((file_name, file_content),))
814+ return self._makeBranchWithTreeAndFiles(((file_name, file_content), ))
815
816 def _makeBranchWithTreeAndFiles(self, files):
817 """Create a branch with a tree that contains the given files.
818@@ -984,7 +987,7 @@
819
820 def _runJobWithFile(self, import_mode, file_name, file_content = None):
821 return self._runJobWithFiles(
822- import_mode, ((file_name, file_content),))
823+ import_mode, ((file_name, file_content), ))
824
825 def _runJobWithFiles(self, import_mode, files,
826 do_upload_translations=False):
827@@ -1018,8 +1021,7 @@
828 pot_name = "foo.pot"
829 entries = self._runJobWithFiles(
830 TranslationsBranchImportMode.IMPORT_TEMPLATES,
831- ((pot_name,), ('eo.po',), ('README',))
832- )
833+ ((pot_name,), ('eo.po',), ('README',)))
834 self.assertEqual(len(entries), 1)
835 entry = entries[0]
836 self.assertEqual(pot_name, entry.path)
837@@ -1058,8 +1060,7 @@
838 pot_name = "en-US.xpi"
839 entries = self._runJobWithFiles(
840 TranslationsBranchImportMode.IMPORT_TEMPLATES,
841- ((pot_name,), ('eo.xpi',), ('README',))
842- )
843+ ((pot_name,), ('eo.xpi',), ('README',)))
844 self.assertEqual(len(entries), 1)
845 entry = entries[0]
846 self.assertEqual(pot_name, entry.path)
847@@ -1108,7 +1109,7 @@
848 pot_name = "foo.pot"
849 revision_id = self._makeBranchWithTreeAndFiles(
850 ((pot_name,), ('eo.po',), ('README',)))
851- self._commitFilesToTree(((pot_name,),))
852+ self._commitFilesToTree(((pot_name, ), ))
853 entries = self._runJob(
854 TranslationsBranchImportMode.IMPORT_TEMPLATES, revision_id)
855 self.assertEqual(len(entries), 1)
856@@ -1120,8 +1121,7 @@
857 # not configured to do so.
858 entries = self._runJobWithFiles(
859 TranslationsBranchImportMode.NO_IMPORT,
860- (('foo.pot',), ('eo.po',), ('README',))
861- )
862+ (('foo.pot',), ('eo.po',), ('README',)))
863 self.assertEqual([], entries)
864
865 def test_upload_translations(self):
866@@ -1183,7 +1183,7 @@
867 # only POTemplate object in the database, if there is only one such
868 # object for this product series.
869 self._makeBranchWithTreeAndFiles(
870- [('foo.pot', None),('bar.pot', None)])
871+ [('foo.pot', None), ('bar.pot', None)])
872 self._makeProductSeries(TranslationsBranchImportMode.IMPORT_TEMPLATES)
873 self.factory.makePOTemplate(self.series, path='foo.pot')
874 self.factory.makePOTemplate(self.series, path='bar.pot')
875@@ -1354,6 +1354,5 @@
876 self.assertFalse(os.path.exists(branch_path))
877
878
879-
880 def test_suite():
881 return TestLoader().loadTestsFromName(__name__)
882
883=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
884--- lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-06-16 19:47:27 +0000
885+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-07-14 16:19:43 +0000
886@@ -1,6 +1,6 @@
887 #!/usr/bin/python
888 #
889-# Copyright 2009 Canonical Ltd. This software is licensed under the
890+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
891 # GNU Affero General Public License version 3 (see the file LICENSE).
892
893 # pylint: disable-msg=W0141
894@@ -21,6 +21,7 @@
895 from zope.security.proxy import removeSecurityProxy
896
897 from canonical.config import config
898+from canonical.launchpad.interfaces.lpstorm import IStore
899 from lp.translations.interfaces.translations import (
900 TranslationsBranchImportMode)
901 from lp.code.interfaces.branchjob import IRosettaUploadJobSource
902@@ -36,7 +37,9 @@
903 def run_as_db_user(username):
904 """Create a decorator that will run a function as the given database user.
905 """
906+
907 def _run_with_different_user(f):
908+
909 def decorated(*args, **kwargs):
910 current_user = LaunchpadZopelessLayer.txn._dbuser
911 if current_user == username:
912@@ -47,6 +50,7 @@
913 finally:
914 LaunchpadZopelessLayer.switchDbUser(current_user)
915 return mergeFunctionMetadata(f, decorated)
916+
917 return _run_with_different_user
918
919
920@@ -94,10 +98,12 @@
921 :return: (num_revisions, num_branch_revisions, num_revision_parents,
922 num_revision_authors)
923 """
924- return (Revision.select().count(),
925- BranchRevision.select().count(),
926- RevisionParent.select().count(),
927- RevisionAuthor.select().count())
928+ store = IStore(Revision)
929+ return (
930+ store.find(Revision).count(),
931+ store.find(BranchRevision).count(),
932+ store.find(RevisionParent).count(),
933+ store.find(RevisionAuthor).count())
934
935 def assertCounts(self, counts, new_revisions=0, new_numbers=0,
936 new_parents=0, new_authors=0):
937@@ -228,10 +234,10 @@
938 :return: A set of tuples (sequence, revision-id) for all the
939 BranchRevisions rows belonging to self.db_branch.
940 """
941- return set(
942- (branch_revision.sequence, branch_revision.revision.revision_id)
943- for branch_revision
944- in BranchRevision.selectBy(branch=db_branch))
945+ return set(IStore(BranchRevision).find(
946+ (BranchRevision.sequence, Revision.revision_id),
947+ Revision.id == BranchRevision.revision_id,
948+ BranchRevision.branch == db_branch))
949
950 def writeToFile(self, filename="file", contents=None):
951 """Set the contents of the specified file.
952@@ -459,8 +465,9 @@
953 # retrieveDatabaseAncestry.
954 branch = getUtility(IBranchLookup).getByUniqueName(
955 '~name12/+junk/junk.contrib')
956- sampledata = list(
957- BranchRevision.selectBy(branch=branch).orderBy('sequence'))
958+ branch_revisions = IStore(BranchRevision).find(
959+ BranchRevision, BranchRevision.branch == branch)
960+ sampledata = list(branch_revisions.order_by(BranchRevision.sequence))
961 expected_ancestry = set(branch_revision.revision.revision_id
962 for branch_revision in sampledata)
963 expected_history = [branch_revision.revision.revision_id
964@@ -540,7 +547,7 @@
965
966 def test_upload_on_new_revision_series_not_configured(self):
967 # Syncing a branch with a changed tip does not create a
968- # new RosettaUploadJob if the linked product series is not
969+ # new RosettaUploadJob if the linked product series is not
970 # configured for translation uploads.
971 self._makeProductSeries()
972 self.commitRevision()
973
974=== modified file 'lib/lp/services/database/prejoin.py'
975--- lib/lp/services/database/prejoin.py 2009-10-28 12:11:35 +0000
976+++ lib/lp/services/database/prejoin.py 2010-07-14 16:19:43 +0000
977@@ -10,6 +10,7 @@
978
979 from lazr.delegates import delegates
980
981+
982 class PrejoinResultSet:
983 """Prejoin support.
984
985@@ -21,12 +22,12 @@
986 The preferred solution is support in Storm core, so we can just do
987 something like:
988
989- >>> results = store.find(Product).prejoin(
990- ... (Person, EmailAddress),
991- ... Product._ownerID == Person.id,
992- ... EmailAddress.personID == Person.id)
993+ # Select Product, but prejoin the owner as well.
994+ >>> join = store.find((Product, Person), Product.name == name)
995+ >>> results = prejoin(join, slice(0, 1))
996 """
997 delegates(IResultSet, context='result_set')
998+
999 def __init__(self, result_set, return_slice=slice(0, 1)):
1000 self.result_set = result_set
1001 self.return_slice = return_slice

Subscribers

People subscribed via source and target branches

to status/vote changes: