Merge lp:~pcjc2/launchpad/fix-tag-search-bug-501945 into lp:launchpad
- fix-tag-search-bug-501945
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Robert Collins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 12200 |
Proposed branch: | lp:~pcjc2/launchpad/fix-tag-search-bug-501945 |
Merge into: | lp:launchpad |
Diff against target: |
611 lines (+259/-193) 2 files modified
lib/lp/bugs/model/bugtask.py (+25/-13) lib/lp/bugs/model/tests/test_bugtask.py (+234/-180) |
To merge this branch: | bzr merge lp:~pcjc2/launchpad/fix-tag-search-bug-501945 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
j.c.sackett (community) | code* | Approve | |
Robert Collins (community) | Approve | ||
Review via email: mp+46075@code.launchpad.net |
Commit message
[r=lifeless]
Description of the change
Improve SQL efficiency when searching for bugs by tag (Pre-filter by Bug.id)
Addresses LP Bug #501945:
Product:+bugs timeout with search for bugs without tags (tag:-*)
The sub-query for tags was not pre-filtered by bug ID, so was causing
a large (and unnecessary) load on the database for some queries, such
as wildcard matches.
A significant speedup is obtained on large data-sets by rephrasing the
SQL sub-query to consider only those BugTag rows which match the current
bug being considered in the outer query.
Also:
Fix argument order when calling assertEqualIgno
This test is passed an expected and observed parameter, but all
callers had the parameters swapped around. This didn't break
anything, but would cause misleading output if a test failed.
Peter Clifton (pcjc2) wrote : | # |
Thanks for the review, I'll update the branch and get back to you.
Peter Clifton (pcjc2) wrote : | # |
The above changes should have addressed the issues raised in review, please check again.
Robert Collins (lifeless) wrote : | # |
Looks good now, thanks.
Robert Collins (lifeless) wrote : | # |
tossing at ec2
Peter Clifton (pcjc2) wrote : | # |
Sorry - it is broken, please hold
Peter Clifton (pcjc2) wrote : | # |
I was obviously too keen.. more haste, less speed etc..
This time I've _definately_ run "bin/test -t bugtask". I clearly hadn't let it finish when I pushed last time.
j.c.sackett (jcsackett) : | # |
Preview Diff
1 | === modified file 'lib/lp/bugs/model/bugtask.py' |
2 | --- lib/lp/bugs/model/bugtask.py 2011-01-05 08:10:10 +0000 |
3 | +++ lib/lp/bugs/model/bugtask.py 2011-01-14 00:41:54 +0000 |
4 | @@ -1354,19 +1354,29 @@ |
5 | |
6 | |
7 | def build_tag_set_query(joiner, tags): |
8 | - """Return an SQL snippet to find bugs matching the given tags. |
9 | + """Return an SQL snippet to find whether a bug matches the given tags. |
10 | |
11 | The tags are sorted so that testing the generated queries is |
12 | easier and more reliable. |
13 | |
14 | + This SQL is designed to be a sub-query where the parent SQL defines |
15 | + Bug.id. It evaluates to TRUE or FALSE, indicating whether the bug |
16 | + with Bug.id matches against the tags passed. |
17 | + |
18 | + Returns None if no tags are passed. |
19 | + |
20 | :param joiner: The SQL set term used to join the individual tag |
21 | clauses, typically "INTERSECT" or "UNION". |
22 | :param tags: An iterable of valid tag names (not prefixed minus |
23 | signs, not wildcards). |
24 | """ |
25 | + if tags == []: |
26 | + return None |
27 | + |
28 | joiner = " %s " % joiner |
29 | - return joiner.join( |
30 | - "SELECT bug FROM BugTag WHERE tag = %s" % quote(tag) |
31 | + return "EXISTS (%s)" % joiner.join( |
32 | + "SELECT TRUE FROM BugTag WHERE " + |
33 | + "BugTag.bug = Bug.id AND BugTag.tag = %s" % quote(tag) |
34 | for tag in sorted(tags)) |
35 | |
36 | |
37 | @@ -1412,23 +1422,25 @@ |
38 | # Search for the *presence* of any tag. |
39 | if '*' in wildcards: |
40 | # Only clobber the clause if not searching for all tags. |
41 | - if len(include_clause) == 0 or not find_all: |
42 | - include_clause = "SELECT bug FROM BugTag" |
43 | + if include_clause == None or not find_all: |
44 | + include_clause = ( |
45 | + "EXISTS (SELECT TRUE FROM BugTag WHERE BugTag.bug = Bug.id)") |
46 | |
47 | # Search for the *absence* of any tag. |
48 | if '-*' in wildcards: |
49 | # Only clobber the clause if searching for all tags. |
50 | - if len(exclude_clause) == 0 or find_all: |
51 | - exclude_clause = "SELECT bug FROM BugTag" |
52 | + if exclude_clause == None or find_all: |
53 | + exclude_clause = ( |
54 | + "EXISTS (SELECT TRUE FROM BugTag WHERE BugTag.bug = Bug.id)") |
55 | |
56 | # Combine the include and exclude sets. |
57 | - if len(include_clause) > 0 and len(exclude_clause) > 0: |
58 | - return "(BugTask.bug IN (%s) %s BugTask.bug NOT IN (%s))" % ( |
59 | + if include_clause != None and exclude_clause != None: |
60 | + return "(%s %s NOT %s)" % ( |
61 | include_clause, combine_with, exclude_clause) |
62 | - elif len(include_clause) > 0: |
63 | - return "BugTask.bug IN (%s)" % include_clause |
64 | - elif len(exclude_clause) > 0: |
65 | - return "BugTask.bug NOT IN (%s)" % exclude_clause |
66 | + elif include_clause != None: |
67 | + return "%s" % include_clause |
68 | + elif exclude_clause != None: |
69 | + return "NOT %s" % exclude_clause |
70 | else: |
71 | # This means that there were no tags (wildcard or specific) to |
72 | # search for (which is allowed, even if it's a bit weird). |
73 | |
74 | === modified file 'lib/lp/bugs/model/tests/test_bugtask.py' |
75 | --- lib/lp/bugs/model/tests/test_bugtask.py 2010-11-12 03:58:03 +0000 |
76 | +++ lib/lp/bugs/model/tests/test_bugtask.py 2011-01-14 00:41:54 +0000 |
77 | @@ -216,300 +216,354 @@ |
78 | # tag. Should be the same for an `any` query or an `all` |
79 | # query. |
80 | expected_query = ( |
81 | - """BugTask.bug IN |
82 | - (SELECT bug FROM BugTag |
83 | - WHERE tag = 'fred')""") |
84 | - self.assertEqualIgnoringWhitespace( |
85 | - self.searchClause(any(u'fred')), |
86 | - expected_query) |
87 | - self.assertEqualIgnoringWhitespace( |
88 | - self.searchClause(all(u'fred')), |
89 | - expected_query) |
90 | + """EXISTS |
91 | + (SELECT TRUE FROM BugTag |
92 | + WHERE BugTag.bug = Bug.id |
93 | + AND BugTag.tag = 'fred')""") |
94 | + self.assertEqualIgnoringWhitespace( |
95 | + expected_query, |
96 | + self.searchClause(any(u'fred'))) |
97 | + self.assertEqualIgnoringWhitespace( |
98 | + expected_query, |
99 | + self.searchClause(all(u'fred'))) |
100 | |
101 | def test_single_tag_absence(self): |
102 | # The WHERE clause to test for the absence of a single |
103 | # tag. Should be the same for an `any` query or an `all` |
104 | # query. |
105 | expected_query = ( |
106 | - """BugTask.bug NOT IN |
107 | - (SELECT bug FROM BugTag |
108 | - WHERE tag = 'fred')""") |
109 | - self.assertEqualIgnoringWhitespace( |
110 | - self.searchClause(any(u'-fred')), |
111 | - expected_query) |
112 | - self.assertEqualIgnoringWhitespace( |
113 | - self.searchClause(all(u'-fred')), |
114 | - expected_query) |
115 | + """NOT EXISTS |
116 | + (SELECT TRUE FROM BugTag |
117 | + WHERE BugTag.bug = Bug.id |
118 | + AND BugTag.tag = 'fred')""") |
119 | + self.assertEqualIgnoringWhitespace( |
120 | + expected_query, |
121 | + self.searchClause(any(u'-fred'))) |
122 | + self.assertEqualIgnoringWhitespace( |
123 | + expected_query, |
124 | + self.searchClause(all(u'-fred'))) |
125 | |
126 | def test_tag_presence(self): |
127 | # The WHERE clause to test for the presence of tags. Should be |
128 | # the same for an `any` query or an `all` query. |
129 | expected_query = ( |
130 | - """BugTask.bug IN |
131 | - (SELECT bug FROM BugTag)""") |
132 | - self.assertEqualIgnoringWhitespace( |
133 | - self.searchClause(any(u'*')), |
134 | - expected_query) |
135 | - self.assertEqualIgnoringWhitespace( |
136 | - self.searchClause(all(u'*')), |
137 | - expected_query) |
138 | + """EXISTS |
139 | + (SELECT TRUE FROM BugTag |
140 | + WHERE BugTag.bug = Bug.id)""") |
141 | + self.assertEqualIgnoringWhitespace( |
142 | + expected_query, |
143 | + self.searchClause(any(u'*'))) |
144 | + self.assertEqualIgnoringWhitespace( |
145 | + expected_query, |
146 | + self.searchClause(all(u'*'))) |
147 | |
148 | def test_tag_absence(self): |
149 | # The WHERE clause to test for the absence of tags. Should be |
150 | # the same for an `any` query or an `all` query. |
151 | expected_query = ( |
152 | - """BugTask.bug NOT IN |
153 | - (SELECT bug FROM BugTag)""") |
154 | - self.assertEqualIgnoringWhitespace( |
155 | - self.searchClause(any(u'-*')), |
156 | - expected_query) |
157 | - self.assertEqualIgnoringWhitespace( |
158 | - self.searchClause(all(u'-*')), |
159 | - expected_query) |
160 | + """NOT EXISTS |
161 | + (SELECT TRUE FROM BugTag |
162 | + WHERE BugTag.bug = Bug.id)""") |
163 | + self.assertEqualIgnoringWhitespace( |
164 | + expected_query, |
165 | + self.searchClause(any(u'-*'))) |
166 | + self.assertEqualIgnoringWhitespace( |
167 | + expected_query, |
168 | + self.searchClause(all(u'-*'))) |
169 | |
170 | def test_multiple_tag_presence_any(self): |
171 | # The WHERE clause to test for the presence of *any* of |
172 | # several tags. |
173 | self.assertEqualIgnoringWhitespace( |
174 | - self.searchClause(any(u'fred', u'bob')), |
175 | - """BugTask.bug IN |
176 | - (SELECT bug FROM BugTag |
177 | - WHERE tag = 'bob' |
178 | + """EXISTS |
179 | + (SELECT TRUE FROM BugTag |
180 | + WHERE BugTag.bug = Bug.id |
181 | + AND BugTag.tag = 'bob' |
182 | UNION |
183 | - SELECT bug FROM BugTag |
184 | - WHERE tag = 'fred')""") |
185 | + SELECT TRUE FROM BugTag |
186 | + WHERE BugTag.bug = Bug.id |
187 | + AND BugTag.tag = 'fred')""", |
188 | + self.searchClause(any(u'fred', u'bob'))) |
189 | # In an `any` query, a positive wildcard is dominant over |
190 | # other positive tags because "bugs with one or more tags" is |
191 | # a superset of "bugs with a specific tag". |
192 | self.assertEqualIgnoringWhitespace( |
193 | - self.searchClause(any(u'fred', u'*')), |
194 | - """BugTask.bug IN |
195 | - (SELECT bug FROM BugTag)""") |
196 | + """EXISTS |
197 | + (SELECT TRUE FROM BugTag |
198 | + WHERE BugTag.bug = Bug.id)""", |
199 | + self.searchClause(any(u'fred', u'*'))) |
200 | |
201 | def test_multiple_tag_absence_any(self): |
202 | # The WHERE clause to test for the absence of *any* of several |
203 | # tags. |
204 | self.assertEqualIgnoringWhitespace( |
205 | - self.searchClause(any(u'-fred', u'-bob')), |
206 | - """BugTask.bug NOT IN |
207 | - (SELECT bug FROM BugTag |
208 | - WHERE tag = 'bob' |
209 | + """NOT EXISTS |
210 | + (SELECT TRUE FROM BugTag |
211 | + WHERE BugTag.bug = Bug.id |
212 | + AND BugTag.tag = 'bob' |
213 | INTERSECT |
214 | - SELECT bug FROM BugTag |
215 | - WHERE tag = 'fred')""") |
216 | + SELECT TRUE FROM BugTag |
217 | + WHERE BugTag.bug = Bug.id |
218 | + AND BugTag.tag = 'fred')""", |
219 | + self.searchClause(any(u'-fred', u'-bob'))) |
220 | # In an `any` query, a negative wildcard is superfluous in the |
221 | # presence of other negative tags because "bugs without a |
222 | # specific tag" is a superset of "bugs without any tags". |
223 | self.assertEqualIgnoringWhitespace( |
224 | - self.searchClause(any(u'-fred', u'-*')), |
225 | - """BugTask.bug NOT IN |
226 | - (SELECT bug FROM BugTag |
227 | - WHERE tag = 'fred')""") |
228 | + """NOT EXISTS |
229 | + (SELECT TRUE FROM BugTag |
230 | + WHERE BugTag.bug = Bug.id |
231 | + AND BugTag.tag = 'fred')""", |
232 | + self.searchClause(any(u'-fred', u'-*'))) |
233 | |
234 | def test_multiple_tag_presence_all(self): |
235 | # The WHERE clause to test for the presence of *all* specified |
236 | # tags. |
237 | self.assertEqualIgnoringWhitespace( |
238 | - self.searchClause(all(u'fred', u'bob')), |
239 | - """BugTask.bug IN |
240 | - (SELECT bug FROM BugTag |
241 | - WHERE tag = 'bob' |
242 | + """EXISTS |
243 | + (SELECT TRUE FROM BugTag |
244 | + WHERE BugTag.bug = Bug.id |
245 | + AND BugTag.tag = 'bob' |
246 | INTERSECT |
247 | - SELECT bug FROM BugTag |
248 | - WHERE tag = 'fred')""") |
249 | + SELECT TRUE FROM BugTag |
250 | + WHERE BugTag.bug = Bug.id |
251 | + AND BugTag.tag = 'fred')""", |
252 | + self.searchClause(all(u'fred', u'bob'))) |
253 | # In an `all` query, a positive wildcard is superfluous in the |
254 | # presence of other positive tags because "bugs with a |
255 | # specific tag" is a subset of (i.e. more specific than) "bugs |
256 | # with one or more tags". |
257 | self.assertEqualIgnoringWhitespace( |
258 | - self.searchClause(all(u'fred', u'*')), |
259 | - """BugTask.bug IN |
260 | - (SELECT bug FROM BugTag |
261 | - WHERE tag = 'fred')""") |
262 | + """EXISTS |
263 | + (SELECT TRUE FROM BugTag |
264 | + WHERE BugTag.bug = Bug.id |
265 | + AND BugTag.tag = 'fred')""", |
266 | + self.searchClause(all(u'fred', u'*'))) |
267 | |
268 | def test_multiple_tag_absence_all(self): |
269 | # The WHERE clause to test for the absence of all specified |
270 | # tags. |
271 | self.assertEqualIgnoringWhitespace( |
272 | - self.searchClause(all(u'-fred', u'-bob')), |
273 | - """BugTask.bug NOT IN |
274 | - (SELECT bug FROM BugTag |
275 | - WHERE tag = 'bob' |
276 | + """NOT EXISTS |
277 | + (SELECT TRUE FROM BugTag |
278 | + WHERE BugTag.bug = Bug.id |
279 | + AND BugTag.tag = 'bob' |
280 | UNION |
281 | - SELECT bug FROM BugTag |
282 | - WHERE tag = 'fred')""") |
283 | + SELECT TRUE FROM BugTag |
284 | + WHERE BugTag.bug = Bug.id |
285 | + AND BugTag.tag = 'fred')""", |
286 | + self.searchClause(all(u'-fred', u'-bob'))) |
287 | # In an `all` query, a negative wildcard is dominant over |
288 | # other negative tags because "bugs without any tags" is a |
289 | # subset of (i.e. more specific than) "bugs without a specific |
290 | # tag". |
291 | self.assertEqualIgnoringWhitespace( |
292 | - self.searchClause(all(u'-fred', u'-*')), |
293 | - """BugTask.bug NOT IN |
294 | - (SELECT bug FROM BugTag)""") |
295 | + """NOT EXISTS |
296 | + (SELECT TRUE FROM BugTag |
297 | + WHERE BugTag.bug = Bug.id)""", |
298 | + self.searchClause(all(u'-fred', u'-*'))) |
299 | |
300 | def test_mixed_tags_any(self): |
301 | # The WHERE clause to test for the presence of one or more |
302 | # specific tags or the absence of one or more other specific |
303 | # tags. |
304 | self.assertEqualIgnoringWhitespace( |
305 | - self.searchClause(any(u'fred', u'-bob')), |
306 | - """(BugTask.bug IN |
307 | - (SELECT bug FROM BugTag |
308 | - WHERE tag = 'fred') |
309 | - OR BugTask.bug NOT IN |
310 | - (SELECT bug FROM BugTag |
311 | - WHERE tag = 'bob'))""") |
312 | + """(EXISTS |
313 | + (SELECT TRUE FROM BugTag |
314 | + WHERE BugTag.bug = Bug.id |
315 | + AND BugTag.tag = 'fred') |
316 | + OR NOT EXISTS |
317 | + (SELECT TRUE FROM BugTag |
318 | + WHERE BugTag.bug = Bug.id |
319 | + AND BugTag.tag = 'bob'))""", |
320 | + self.searchClause(any(u'fred', u'-bob'))) |
321 | self.assertEqualIgnoringWhitespace( |
322 | - self.searchClause(any(u'fred', u'-bob', u'eric', u'-harry')), |
323 | - """(BugTask.bug IN |
324 | - (SELECT bug FROM BugTag |
325 | - WHERE tag = 'eric' |
326 | + """(EXISTS |
327 | + (SELECT TRUE FROM BugTag |
328 | + WHERE BugTag.bug = Bug.id |
329 | + AND BugTag.tag = 'eric' |
330 | UNION |
331 | - SELECT bug FROM BugTag |
332 | - WHERE tag = 'fred') |
333 | - OR BugTask.bug NOT IN |
334 | - (SELECT bug FROM BugTag |
335 | - WHERE tag = 'bob' |
336 | + SELECT TRUE FROM BugTag |
337 | + WHERE BugTag.bug = Bug.id |
338 | + AND BugTag.tag = 'fred') |
339 | + OR NOT EXISTS |
340 | + (SELECT TRUE FROM BugTag |
341 | + WHERE BugTag.bug = Bug.id |
342 | + AND BugTag.tag = 'bob' |
343 | INTERSECT |
344 | - SELECT bug FROM BugTag |
345 | - WHERE tag = 'harry'))""") |
346 | + SELECT TRUE FROM BugTag |
347 | + WHERE BugTag.bug = Bug.id |
348 | + AND BugTag.tag = 'harry'))""", |
349 | + self.searchClause(any(u'fred', u'-bob', u'eric', u'-harry'))) |
350 | # The positive wildcard is dominant over other positive tags. |
351 | self.assertEqualIgnoringWhitespace( |
352 | - self.searchClause(any(u'fred', u'-bob', u'*', u'-harry')), |
353 | - """(BugTask.bug IN |
354 | - (SELECT bug FROM BugTag) |
355 | - OR BugTask.bug NOT IN |
356 | - (SELECT bug FROM BugTag |
357 | - WHERE tag = 'bob' |
358 | + """(EXISTS |
359 | + (SELECT TRUE FROM BugTag |
360 | + WHERE BugTag.bug = Bug.id) |
361 | + OR NOT EXISTS |
362 | + (SELECT TRUE FROM BugTag |
363 | + WHERE BugTag.bug = Bug.id |
364 | + AND BugTag.tag = 'bob' |
365 | INTERSECT |
366 | - SELECT bug FROM BugTag |
367 | - WHERE tag = 'harry'))""") |
368 | + SELECT TRUE FROM BugTag |
369 | + WHERE BugTag.bug = Bug.id |
370 | + AND BugTag.tag = 'harry'))""", |
371 | + self.searchClause(any(u'fred', u'-bob', u'*', u'-harry'))) |
372 | # The negative wildcard is superfluous in the presence of |
373 | # other negative tags. |
374 | self.assertEqualIgnoringWhitespace( |
375 | - self.searchClause(any(u'fred', u'-bob', u'eric', u'-*')), |
376 | - """(BugTask.bug IN |
377 | - (SELECT bug FROM BugTag |
378 | - WHERE tag = 'eric' |
379 | + """(EXISTS |
380 | + (SELECT TRUE FROM BugTag |
381 | + WHERE BugTag.bug = Bug.id |
382 | + AND BugTag.tag = 'eric' |
383 | UNION |
384 | - SELECT bug FROM BugTag |
385 | - WHERE tag = 'fred') |
386 | - OR BugTask.bug NOT IN |
387 | - (SELECT bug FROM BugTag |
388 | - WHERE tag = 'bob'))""") |
389 | + SELECT TRUE FROM BugTag |
390 | + WHERE BugTag.bug = Bug.id |
391 | + AND BugTag.tag = 'fred') |
392 | + OR NOT EXISTS |
393 | + (SELECT TRUE FROM BugTag |
394 | + WHERE BugTag.bug = Bug.id |
395 | + AND BugTag.tag = 'bob'))""", |
396 | + self.searchClause(any(u'fred', u'-bob', u'eric', u'-*'))) |
397 | # The negative wildcard is not superfluous in the absence of |
398 | # other negative tags. |
399 | self.assertEqualIgnoringWhitespace( |
400 | - self.searchClause(any(u'fred', u'-*', u'eric')), |
401 | - """(BugTask.bug IN |
402 | - (SELECT bug FROM BugTag |
403 | - WHERE tag = 'eric' |
404 | + """(EXISTS |
405 | + (SELECT TRUE FROM BugTag |
406 | + WHERE BugTag.bug = Bug.id |
407 | + AND BugTag.tag = 'eric' |
408 | UNION |
409 | - SELECT bug FROM BugTag |
410 | - WHERE tag = 'fred') |
411 | - OR BugTask.bug NOT IN |
412 | - (SELECT bug FROM BugTag))""") |
413 | + SELECT TRUE FROM BugTag |
414 | + WHERE BugTag.bug = Bug.id |
415 | + AND BugTag.tag = 'fred') |
416 | + OR NOT EXISTS |
417 | + (SELECT TRUE FROM BugTag |
418 | + WHERE BugTag.bug = Bug.id))""", |
419 | + self.searchClause(any(u'fred', u'-*', u'eric'))) |
420 | # The positive wildcard is dominant over other positive tags, |
421 | # and the negative wildcard is superfluous in the presence of |
422 | # other negative tags. |
423 | self.assertEqualIgnoringWhitespace( |
424 | - self.searchClause(any(u'fred', u'-*', u'*', u'-harry')), |
425 | - """(BugTask.bug IN |
426 | - (SELECT bug FROM BugTag) |
427 | - OR BugTask.bug NOT IN |
428 | - (SELECT bug FROM BugTag |
429 | - WHERE tag = 'harry'))""") |
430 | + """(EXISTS |
431 | + (SELECT TRUE FROM BugTag |
432 | + WHERE BugTag.bug = Bug.id) |
433 | + OR NOT EXISTS |
434 | + (SELECT TRUE FROM BugTag |
435 | + WHERE BugTag.bug = Bug.id |
436 | + AND BugTag.tag = 'harry'))""", |
437 | + self.searchClause(any(u'fred', u'-*', u'*', u'-harry'))) |
438 | |
439 | def test_mixed_tags_all(self): |
440 | # The WHERE clause to test for the presence of one or more |
441 | # specific tags and the absence of one or more other specific |
442 | # tags. |
443 | self.assertEqualIgnoringWhitespace( |
444 | - self.searchClause(all(u'fred', u'-bob')), |
445 | - """(BugTask.bug IN |
446 | - (SELECT bug FROM BugTag |
447 | - WHERE tag = 'fred') |
448 | - AND BugTask.bug NOT IN |
449 | - (SELECT bug FROM BugTag |
450 | - WHERE tag = 'bob'))""") |
451 | + """(EXISTS |
452 | + (SELECT TRUE FROM BugTag |
453 | + WHERE BugTag.bug = Bug.id |
454 | + AND BugTag.tag = 'fred') |
455 | + AND NOT EXISTS |
456 | + (SELECT TRUE FROM BugTag |
457 | + WHERE BugTag.bug = Bug.id |
458 | + AND BugTag.tag = 'bob'))""", |
459 | + self.searchClause(all(u'fred', u'-bob'))) |
460 | self.assertEqualIgnoringWhitespace( |
461 | - self.searchClause(all(u'fred', u'-bob', u'eric', u'-harry')), |
462 | - """(BugTask.bug IN |
463 | - (SELECT bug FROM BugTag |
464 | - WHERE tag = 'eric' |
465 | + """(EXISTS |
466 | + (SELECT TRUE FROM BugTag |
467 | + WHERE BugTag.bug = Bug.id |
468 | + AND BugTag.tag = 'eric' |
469 | INTERSECT |
470 | - SELECT bug FROM BugTag |
471 | - WHERE tag = 'fred') |
472 | - AND BugTask.bug NOT IN |
473 | - (SELECT bug FROM BugTag |
474 | - WHERE tag = 'bob' |
475 | + SELECT TRUE FROM BugTag |
476 | + WHERE BugTag.bug = Bug.id |
477 | + AND BugTag.tag = 'fred') |
478 | + AND NOT EXISTS |
479 | + (SELECT TRUE FROM BugTag |
480 | + WHERE BugTag.bug = Bug.id |
481 | + AND BugTag.tag = 'bob' |
482 | UNION |
483 | - SELECT bug FROM BugTag |
484 | - WHERE tag = 'harry'))""") |
485 | + SELECT TRUE FROM BugTag |
486 | + WHERE BugTag.bug = Bug.id |
487 | + AND BugTag.tag = 'harry'))""", |
488 | + self.searchClause(all(u'fred', u'-bob', u'eric', u'-harry'))) |
489 | # The positive wildcard is superfluous in the presence of |
490 | # other positive tags. |
491 | self.assertEqualIgnoringWhitespace( |
492 | - self.searchClause(all(u'fred', u'-bob', u'*', u'-harry')), |
493 | - """(BugTask.bug IN |
494 | - (SELECT bug FROM BugTag |
495 | - WHERE tag = 'fred') |
496 | - AND BugTask.bug NOT IN |
497 | - (SELECT bug FROM BugTag |
498 | - WHERE tag = 'bob' |
499 | + """(EXISTS |
500 | + (SELECT TRUE FROM BugTag |
501 | + WHERE BugTag.bug = Bug.id |
502 | + AND BugTag.tag = 'fred') |
503 | + AND NOT EXISTS |
504 | + (SELECT TRUE FROM BugTag |
505 | + WHERE BugTag.bug = Bug.id |
506 | + AND BugTag.tag = 'bob' |
507 | UNION |
508 | - SELECT bug FROM BugTag |
509 | - WHERE tag = 'harry'))""") |
510 | + SELECT TRUE FROM BugTag |
511 | + WHERE BugTag.bug = Bug.id |
512 | + AND BugTag.tag = 'harry'))""", |
513 | + self.searchClause(all(u'fred', u'-bob', u'*', u'-harry'))) |
514 | # The positive wildcard is not superfluous in the absence of |
515 | # other positive tags. |
516 | self.assertEqualIgnoringWhitespace( |
517 | - self.searchClause(all(u'-bob', u'*', u'-harry')), |
518 | - """(BugTask.bug IN |
519 | - (SELECT bug FROM BugTag) |
520 | - AND BugTask.bug NOT IN |
521 | - (SELECT bug FROM BugTag |
522 | - WHERE tag = 'bob' |
523 | + """(EXISTS |
524 | + (SELECT TRUE FROM BugTag |
525 | + WHERE BugTag.bug = Bug.id) |
526 | + AND NOT EXISTS |
527 | + (SELECT TRUE FROM BugTag |
528 | + WHERE BugTag.bug = Bug.id |
529 | + AND BugTag.tag = 'bob' |
530 | UNION |
531 | - SELECT bug FROM BugTag |
532 | - WHERE tag = 'harry'))""") |
533 | + SELECT TRUE FROM BugTag |
534 | + WHERE BugTag.bug = Bug.id |
535 | + AND BugTag.tag = 'harry'))""", |
536 | + self.searchClause(all(u'-bob', u'*', u'-harry'))) |
537 | # The negative wildcard is dominant over other negative tags. |
538 | self.assertEqualIgnoringWhitespace( |
539 | - self.searchClause(all(u'fred', u'-bob', u'eric', u'-*')), |
540 | - """(BugTask.bug IN |
541 | - (SELECT bug FROM BugTag |
542 | - WHERE tag = 'eric' |
543 | + """(EXISTS |
544 | + (SELECT TRUE FROM BugTag |
545 | + WHERE BugTag.bug = Bug.id |
546 | + AND BugTag.tag = 'eric' |
547 | INTERSECT |
548 | - SELECT bug FROM BugTag |
549 | - WHERE tag = 'fred') |
550 | - AND BugTask.bug NOT IN |
551 | - (SELECT bug FROM BugTag))""") |
552 | + SELECT TRUE FROM BugTag |
553 | + WHERE BugTag.bug = Bug.id |
554 | + AND BugTag.tag = 'fred') |
555 | + AND NOT EXISTS |
556 | + (SELECT TRUE FROM BugTag |
557 | + WHERE BugTag.bug = Bug.id))""", |
558 | + self.searchClause(all(u'fred', u'-bob', u'eric', u'-*'))) |
559 | # The positive wildcard is superfluous in the presence of |
560 | # other positive tags, and the negative wildcard is dominant |
561 | # over other negative tags. |
562 | self.assertEqualIgnoringWhitespace( |
563 | - self.searchClause(all(u'fred', u'-*', u'*', u'-harry')), |
564 | - """(BugTask.bug IN |
565 | - (SELECT bug FROM BugTag |
566 | - WHERE tag = 'fred') |
567 | - AND BugTask.bug NOT IN |
568 | - (SELECT bug FROM BugTag))""") |
569 | + """(EXISTS |
570 | + (SELECT TRUE FROM BugTag |
571 | + WHERE BugTag.bug = Bug.id |
572 | + AND BugTag.tag = 'fred') |
573 | + AND NOT EXISTS |
574 | + (SELECT TRUE FROM BugTag |
575 | + WHERE BugTag.bug = Bug.id))""", |
576 | + self.searchClause(all(u'fred', u'-*', u'*', u'-harry'))) |
577 | |
578 | def test_mixed_wildcards(self): |
579 | # The WHERE clause to test for the presence of tags or the |
580 | # absence of tags. |
581 | self.assertEqualIgnoringWhitespace( |
582 | - self.searchClause(any(u'*', u'-*')), |
583 | - """(BugTask.bug IN |
584 | - (SELECT bug FROM BugTag) |
585 | - OR BugTask.bug NOT IN |
586 | - (SELECT bug FROM BugTag))""") |
587 | + """(EXISTS |
588 | + (SELECT TRUE FROM BugTag |
589 | + WHERE BugTag.bug = Bug.id) |
590 | + OR NOT EXISTS |
591 | + (SELECT TRUE FROM BugTag |
592 | + WHERE BugTag.bug = Bug.id))""", |
593 | + self.searchClause(any(u'*', u'-*'))) |
594 | # The WHERE clause to test for the presence of tags and the |
595 | # absence of tags. |
596 | self.assertEqualIgnoringWhitespace( |
597 | - self.searchClause(all(u'*', u'-*')), |
598 | - """(BugTask.bug IN |
599 | - (SELECT bug FROM BugTag) |
600 | - AND BugTask.bug NOT IN |
601 | - (SELECT bug FROM BugTag))""") |
602 | + """(EXISTS |
603 | + (SELECT TRUE FROM BugTag |
604 | + WHERE BugTag.bug = Bug.id) |
605 | + AND NOT EXISTS |
606 | + (SELECT TRUE FROM BugTag |
607 | + WHERE BugTag.bug = Bug.id))""", |
608 | + self.searchClause(all(u'*', u'-*'))) |
609 | |
610 | |
611 | class TestBugTaskHardwareSearch(TestCaseWithFactory): |
Peter--
This looks pretty good. There are just two simple things I need fixed below:
> === modified file 'lib/lp/ bugs/model/ bugtask. py' bugs/model/ bugtask. py 2011-01-05 08:10:10 +0000 bugs/model/ bugtask. py 2011-01-13 01:38:23 +0000
> --- lib/lp/
> +++ lib/lp/
> @@ -1366,7 +1366,8 @@
> """
> joiner = " %s " % joiner
> return joiner.join(
> - "SELECT bug FROM BugTag WHERE tag = %s" % quote(tag)
> + "SELECT TRUE FROM BugTag WHERE " +
> + "BugTag.bug = Bug.id AND BugTag.tag = %s" % quote(tag)
> for tag in sorted(tags))
> @@ -1413,22 +1414,24 @@
> if '*' in wildcards:
> # Only clobber the clause if not searching for all tags.
> if len(include_clause) == 0 or not find_all:
> - include_clause = "SELECT bug FROM BugTag"
> + include_clause = (
> + "SELECT TRUE FROM BugTag WHERE BugTag.bug = Bug.id")
>
> # Search for the *absence* of any tag.
> if '-*' in wildcards:
> # Only clobber the clause if searching for all tags.
> if len(exclude_clause) == 0 or find_all:
> - exclude_clause = "SELECT bug FROM BugTag"
> + exclude_clause = (
> + "SELECT TRUE FROM BugTag WHERE BugTag.bug = Bug.id")
>
> # Combine the include and exclude sets.
> if len(include_clause) > 0 and len(exclude_clause) > 0:
> - return "(BugTask.bug IN (%s) %s BugTask.bug NOT IN (%s))" % (
> + return "(EXISTS (%s) %s NOT EXISTS (%s))" % (
> include_clause, combine_with, exclude_clause)
> elif len(include_clause) > 0:
> - return "BugTask.bug IN (%s)" % include_clause
> + return "EXISTS (%s)" % include_clause
> elif len(exclude_clause) > 0:
> - return "BugTask.bug NOT IN (%s)" % exclude_clause
> + return "NOT EXISTS (%s)" % exclude_clause
> else:
> # This means that there were no tags (wildcard or specific) to
> # search for (which is allowed, even if it's a bit weird).
This would be much easier to understand if there was some comment at the start explaining that use of EXISTS and NOT EXISTS in the final query using the include/exclude part means you only need to get a result set, not the actual bugtags, which is why you're selecting TRUE.
> === modified file 'lib/lp/ bugs/model/ tests/test_ bugtask. py' bugs/model/ tests/test_ bugtask. py 2010-11-12 03:58:03 +0000 bugs/model/ tests/test_ bugtask. py 2011-01-13 01:38:23 +0000 search_ clause( tag_spec) ringWhitespace( self, expected, observed): ringWhitespace( self, observed, expected): whitespace( expected) , whitespace( observed) ) whitespace( observed) , whitespace( expected) )
> --- lib/lp/
> +++ lib/lp/
> @@ -201,10 +201,10 @@
> def searchClause(self, tag_spec):
> return build_tag_
>
> - def assertEqualIgno
> + def assertEqualIgno
> return self.assertEqual(
> - normalize_
> - normalize_
> + normalize_
> + normalize_
You should flip those back; by convention we put the expected value first in assertEqual statements.