Merge lp:~leonardr/launchpad/optimized-length into lp:launchpad/db-devel

Proposed by Leonard Richardson on 2010-08-19
Status: Merged
Merged at revision: 9678
Proposed branch: lp:~leonardr/launchpad/optimized-length
Merge into: lp:launchpad/db-devel
Diff against target: 144 lines (+72/-9)
4 files modified
lib/canonical/launchpad/doc/batch_navigation.txt (+28/-2)
lib/canonical/launchpad/pagetests/webservice/datamodel.txt (+35/-0)
lib/canonical/launchpad/pagetests/webservice/multiversion.txt (+8/-6)
versions.cfg (+1/-1)
To merge this branch: bzr merge lp:~leonardr/launchpad/optimized-length
Reviewer Review Type Date Requested Status
Māris Fogels (community) 2010-08-19 Approve on 2010-08-19
Review via email: mp+33132@code.launchpad.net

Description of the Change

The version of lazr.batchnavigator currently integrated into Launchpad causes the web service to raise a Storm exception in a few simple cases. I was able to land the bad version because we didn't test this particular code path--it's tested in lazr.restful, but lazr.restful doesn't use Storm.

This branch tests the code path that fails, and integrates a new version of lazr.batchnavigator into Launchpad to make it stop failing.

Some facts about the new version of lazr.batchnavigator:

1. I've optimized it to avoid triggering the code that causes the exception. This is good on its own, because even if it worked, that code would be very inefficient.

2. The new version can occasionally tell you the length of a collection without sending out a COUNT query. I added Launchpad tests for this at the SQL-peeking level. Unfortunately, we can't really take advantage of this feature right now--the way I found out about these failures in the first place was while integrating a version of lazr.restful that took advantage of the feature. If I start taking advantage of this feature, the datamodel.txt tests I've added will start failing again.

To post a comment you must log in.
Māris Fogels (mars) wrote :

Hi Leonard,

This looks great! r=mars.

Maris

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/doc/batch_navigation.txt'
2--- lib/canonical/launchpad/doc/batch_navigation.txt 2010-08-12 15:03:51 +0000
3+++ lib/canonical/launchpad/doc/batch_navigation.txt 2010-08-19 16:54:54 +0000
4@@ -89,14 +89,38 @@
5 True
6
7 As seen above, simply accessing the batch doesn't trigger a SQL query
8-asking for the length. But explicitly asking for the length will
9-trigger a SQL query.
10+asking for the length of the entire resultset. But explicitly asking
11+for the length will trigger a SQL query in most circumstances.
12
13 >>> CursorWrapper.last_executed_sql = []
14 >>> ignored = email_batch.total()
15 >>> print CursorWrapper.last_executed_sql[0]
16 SELECT COUNT(*) FROM EmailAddress
17
18+There are exceptions. When the current batch is the last one in the
19+list, it's possible to get the length of the entire resultset without
20+triggering a COUNT query.
21+
22+ >>> CursorWrapper.last_executed_sql = []
23+ >>> batch_nav = BatchNavigator(select_results, build_request(), size=50)
24+ >>> final_batch = batch_nav.currentBatch().nextBatch()
25+ >>> batch_items = list(final_batch)
26+ >>> ignored = final_batch.total()
27+ >>> print "\n".join(CursorWrapper.last_executed_sql)
28+ SELECT ... FROM EmailAddress ... OFFSET 0
29+ SELECT ... FROM EmailAddress ... OFFSET 50
30+
31+When the current batch contains the entire resultset, it's possible to
32+get the length of the resultset without triggering a COUNT query.
33+
34+ >>> CursorWrapper.last_executed_sql = []
35+ >>> one_page_nav = BatchNavigator(select_results, build_request(), size=200)
36+ >>> only_batch = one_page_nav.currentBatch()
37+ >>> batch_items = list(only_batch)
38+ >>> ignored = only_batch.total()
39+ >>> print "\n".join(CursorWrapper.last_executed_sql)
40+ SELECT ... FROM EmailAddress ... OFFSET 0
41+
42 Multiple pages
43 ==============
44
45@@ -105,6 +129,8 @@
46 >>> batch_nav.has_multiple_pages
47 True
48
49+ >>> one_page_nav.has_multiple_pages
50+ False
51
52 Maximum batch size
53 ==================
54
55=== added file 'lib/canonical/launchpad/pagetests/webservice/datamodel.txt'
56--- lib/canonical/launchpad/pagetests/webservice/datamodel.txt 1970-01-01 00:00:00 +0000
57+++ lib/canonical/launchpad/pagetests/webservice/datamodel.txt 2010-08-19 16:54:54 +0000
58@@ -0,0 +1,35 @@
59+***************************
60+End-to-end data model tests
61+***************************
62+
63+The lazr.restful tests use in-memory Python objects, and Launchpad
64+uses Storm backed by a database. This means it's nice to have some
65+end-to-end tests of code paths that, on the surface, look like they're
66+already tested in lazr.restful.
67+
68+ >>> def get_collection(version, start=0, size=2):
69+ ... collection = webservice.get(
70+ ... ("/people?ws.op=find&text=s&ws.start=%s&ws.size=%s" %
71+ ... (start, size)),
72+ ... api_version=version)
73+ ... return collection.jsonBody()
74+
75+Even if an entire collection fits on one page (so that the total size
76+of the collection is obvious), 'total_size_link' will be served
77+instead of 'total_size'. (It would be nice to fix this.)
78+
79+ >>> collection = get_collection("devel", size=100)
80+ >>> print sorted(collection.keys())
81+ [u'entries', u'start', u'total_size_link']
82+ >>> print webservice.get(collection['total_size_link']).jsonBody()
83+ 9
84+
85+Even if the last page of the collection is fetched (so that the total
86+size of the collection is semi-obvious), 'total_size_link' will be
87+served instead of 'total_size'. (It would be nice to fix this.)
88+
89+ >>> collection = get_collection("devel", start=8)
90+ >>> print sorted(collection.keys())
91+ [u'entries', u'prev_collection_link', u'start', u'total_size_link']
92+ >>> print webservice.get(collection['total_size_link']).jsonBody()
93+ 9
94
95=== modified file 'lib/canonical/launchpad/pagetests/webservice/multiversion.txt'
96--- lib/canonical/launchpad/pagetests/webservice/multiversion.txt 2010-08-13 17:22:53 +0000
97+++ lib/canonical/launchpad/pagetests/webservice/multiversion.txt 2010-08-19 16:54:54 +0000
98@@ -9,25 +9,27 @@
99 return collections will return a 'total_size_link' pointing to the
100 total size of the collection.
101
102- >>> def get_collection(version):
103+ >>> def get_collection(version, start=0, size=2):
104 ... collection = webservice.get(
105- ... "/people?ws.op=find&text=salgado", api_version=version)
106+ ... ("/people?ws.op=find&text=s&ws.start=%s&ws.size=%s" %
107+ ... (start, size)),
108+ ... api_version=version)
109 ... return collection.jsonBody()
110
111 >>> collection = get_collection("devel")
112 >>> print sorted(collection.keys())
113- [u'entries', u'start', u'total_size_link']
114+ [u'entries', u'next_collection_link', u'start', u'total_size_link']
115 >>> print webservice.get(collection['total_size_link']).jsonBody()
116- 1
117+ 9
118
119 In previous versions, the same named operations will return a
120 'total_size' containing the actual size of the collection.
121
122 >>> collection = get_collection("1.0")
123 >>> print sorted(collection.keys())
124- [u'entries', u'start', u'total_size']
125+ [u'entries', u'next_collection_link', u'start', u'total_size']
126 >>> print collection['total_size']
127- 1
128+ 9
129
130 Mutator operations
131 ==================
132
133=== modified file 'versions.cfg'
134--- versions.cfg 2010-08-17 20:20:28 +0000
135+++ versions.cfg 2010-08-19 16:54:54 +0000
136@@ -26,7 +26,7 @@
137 ipython = 0.9.1
138 launchpadlib = 1.6.4
139 lazr.authentication = 0.1.1
140-lazr.batchnavigator = 1.2.1
141+lazr.batchnavigator = 1.2.2
142 lazr.config = 1.1.3
143 lazr.delegates = 1.2.0
144 lazr.enum = 1.1.2

Subscribers

People subscribed via source and target branches

to status/vote changes: