Merge lp:~adeuring/launchpad/bug-739052-3 into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 13602
Proposed branch: lp:~adeuring/launchpad/bug-739052-3
Merge into: lp:launchpad
Prerequisite: lp:~adeuring/launchpad/bug-739052-2
Diff against target: 182 lines (+78/-16)
2 files modified
lib/canonical/launchpad/webapp/batching.py (+36/-4)
lib/canonical/launchpad/webapp/tests/test_batching.py (+42/-12)
To merge this branch: bzr merge lp:~adeuring/launchpad/bug-739052-3
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+70272@code.launchpad.net

Commit message

[r=lifeless][no-qa] Use a WHERE clause like (col1, col2,..) < (memo1, memo2,...) if sort columns use only ascending order or or only descending order

Description of the change

StormRangeFactory: This branch implements sort expressions like (col1, col2...) > (memo1, momo2...) if all sort columns use only ascending or only descending order.

./bin/test canonical -vvt canonical.launchpad.webapp.tests.test_batching.TestStormRangeFactory

no lint

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

Nice incremental step.

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/webapp/batching.py'
2--- lib/canonical/launchpad/webapp/batching.py 2011-08-03 10:21:35 +0000
3+++ lib/canonical/launchpad/webapp/batching.py 2011-08-03 10:21:37 +0000
4@@ -11,8 +11,10 @@
5 from storm import Undef
6 from storm.expr import (
7 And,
8+ compile,
9 Desc,
10 Or,
11+ SQL,
12 )
13 from storm.properties import PropertyColumn
14 from storm.zope.interfaces import IResultSet
15@@ -26,6 +28,7 @@
16 )
17
18 from canonical.config import config
19+from canonical.database.sqlbase import sqlvalues
20 from canonical.launchpad.components.decoratedresultset import (
21 DecoratedResultSet,
22 )
23@@ -342,7 +345,7 @@
24 return expression
25 return [plain_expression(column) == memo for column, memo in limits]
26
27- def whereExpressions(self, limits):
28+ def genericWhereExpressions(self, limits):
29 """Generate WHERE expressions for the given sort columns and
30 memos values.
31
32@@ -371,14 +374,43 @@
33 clauses = self.andClausesForLeadingColumns(start)
34 clauses.append(last_limit)
35 clause_for_last_column = reduce(And, clauses)
36- return [clause_for_last_column] + self.whereExpressions(start)
37+ return (
38+ [clause_for_last_column]
39+ + self.genericWhereExpressions(start))
40 else:
41 return [last_limit]
42
43+ def whereExpressions(self, sort_expressions, memos):
44+ """WHERE expressions for the given sort columns and memos values."""
45+ expression = sort_expressions[0]
46+ descending = isinstance(expression, Desc)
47+ consistent = True
48+ for expression in sort_expressions[1:]:
49+ if isinstance(expression, Desc) != descending:
50+ consistent = False
51+ break
52+ if not consistent or len(sort_expressions) == 1:
53+ return self.genericWhereExpressions(zip(sort_expressions, memos))
54+
55+ # If the columns are sorted either only ascending or only
56+ # descending, we can specify a single WHERE condition
57+ # (col1, col2...) > (memo1, memo2...)
58+ if descending:
59+ sort_expressions = [
60+ expression.expr for expression in sort_expressions]
61+ sort_expressions = map(compile, sort_expressions)
62+ sort_expressions = ', '.join(sort_expressions)
63+ memos = sqlvalues(*memos)
64+ memos = ', '.join(memos)
65+ if descending:
66+ return [SQL('(%s) < (%s)' % (sort_expressions, memos))]
67+ else:
68+ return [SQL('(%s) > (%s)' % (sort_expressions, memos))]
69+
70 def getSliceFromMemo(self, size, memo):
71 """Return a result set for the given memo values.
72
73- Note that at least two other implementatians are possible:
74+ Note that at least two other implementations are possible:
75 Instead of OR-combining the expressions returned by
76 whereExpressions(), these expressions could be used for
77 separate SELECTs which are then merged with UNION ALL.
78@@ -390,7 +422,7 @@
79 differ between different queries.
80 """
81 sort_expressions = self.getOrderBy()
82- where = self.whereExpressions(zip(sort_expressions, memo))
83+ where = self.whereExpressions(sort_expressions, memo)
84 where = reduce(Or, where)
85 # From storm.zope.interfaces.IResultSet.__doc__:
86 # - C{find()}, C{group_by()} and C{having()} are really
87
88=== modified file 'lib/canonical/launchpad/webapp/tests/test_batching.py'
89--- lib/canonical/launchpad/webapp/tests/test_batching.py 2011-08-03 10:21:35 +0000
90+++ lib/canonical/launchpad/webapp/tests/test_batching.py 2011-08-03 10:21:37 +0000
91@@ -93,7 +93,7 @@
92 StormRangeFactoryError, range_factory.getOrderValuesFor,
93 resultset[0])
94 self.assertEqual(
95- "StormRangeFactory supports only sorting by PropertyColumn, "
96+ "StormRangeFactory only supports sorting by PropertyColumn, "
97 "not by 'Person.id'.",
98 str(exception))
99
100@@ -106,7 +106,7 @@
101 resultset[0])
102 self.assertTrue(
103 str(exception).startswith(
104- 'StormRangeFactory supports only sorting by PropertyColumn, '
105+ 'StormRangeFactory only supports sorting by PropertyColumn, '
106 'not by <storm.expr.SQL object at'))
107
108 def test_getOrderValuesFor__unordered_result_set(self):
109@@ -358,7 +358,7 @@
110 """
111 resultset = self.makeStormResultSet()
112 range_factory = StormRangeFactory(resultset, self.logError)
113- [where_clause] = range_factory.whereExpressions([(Person.id, 1)])
114+ [where_clause] = range_factory.whereExpressions([Person.id], [1])
115 self.assertEquals('Person.id > ?', compile(where_clause))
116
117 def test_whereExpressions_desc(self):
118@@ -368,15 +368,45 @@
119 resultset = self.makeStormResultSet()
120 range_factory = StormRangeFactory(resultset, self.logError)
121 [where_clause] = range_factory.whereExpressions(
122- [(Desc(Person.id), 1)])
123+ [Desc(Person.id)], [1])
124 self.assertEquals('Person.id < ?', compile(where_clause))
125
126- def test_whereExpressions__two_sort_columns(self):
127- """If the sort columns and memo values (c1, m1) and (c2, m2)
128- are specified, whereExpressions() returns two expressions where
129- the first expression is
130-
131- c1 == m1 AND c2 > m2
132+ def test_whereExpressions__two_sort_columns_asc_asc(self):
133+ """If the ascending sort columns c1, c2 and the memo values
134+ m1, m2 are specified, whereExpressions() returns a WHERE
135+ expressions comparing the tuple (c1, c2) with the memo tuple
136+ (m1, m2):
137+
138+ (c1, c2) > (m1, m2)
139+ """
140+ resultset = self.makeStormResultSet()
141+ range_factory = StormRangeFactory(resultset, self.logError)
142+ [where_clause] = range_factory.whereExpressions(
143+ [Person.id, Person.name], [1, 'foo'])
144+ self.assertEquals(
145+ "(Person.id, Person.name) > (1, 'foo')", compile(where_clause))
146+
147+ def test_whereExpressions__two_sort_columns_desc_desc(self):
148+ """If the descending sort columns c1, c2 and the memo values
149+ m1, m2 are specified, whereExpressions() returns a WHERE
150+ expressions comparing the tuple (c1, c2) with the memo tuple
151+ (m1, m2):
152+
153+ (c1, c2) < (m1, m2)
154+ """
155+ resultset = self.makeStormResultSet()
156+ range_factory = StormRangeFactory(resultset, self.logError)
157+ [where_clause] = range_factory.whereExpressions(
158+ [Desc(Person.id), Desc(Person.name)], [1, 'foo'])
159+ self.assertEquals(
160+ "(Person.id, Person.name) < (1, 'foo')", compile(where_clause))
161+
162+ def test_whereExpressions__two_sort_columns_asc_desc(self):
163+ """If the ascending sort column c1, the descending sort column
164+ c2 and the memo values m1, m2 are specified, whereExpressions()
165+ returns two expressions where the first expression is
166+
167+ c1 == m1 AND c2 < m2
168
169 and the second expression is
170
171@@ -385,9 +415,9 @@
172 resultset = self.makeStormResultSet()
173 range_factory = StormRangeFactory(resultset, self.logError)
174 [where_clause_1, where_clause_2] = range_factory.whereExpressions(
175- [(Person.id, 1), (Person.name, 'foo')])
176+ [Person.id, Desc(Person.name)], [1, 'foo'])
177 self.assertEquals(
178- 'Person.id = ? AND Person.name > ?', compile(where_clause_1))
179+ 'Person.id = ? AND Person.name < ?', compile(where_clause_1))
180 self.assertEquals('Person.id > ?', compile(where_clause_2))
181
182 def test_getSlice__forward_without_memo(self):