Merge lp:~benji/launchpad/bug-697735 into lp:launchpad
- bug-697735
- Merge into devel
Status: | Work in progress | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~benji/launchpad/bug-697735 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
1661 lines (+145/-1310) 9 files modified
database/schema/patch-2208-63-0.sql (+0/-597) database/schema/patch-2208-63-1.sql (+0/-460) database/schema/patch-2208-63-2.sql (+0/-13) database/schema/patch-2208-63-3.sql (+0/-55) database/schema/patch-2208-63-4.sql (+0/-171) lib/canonical/launchpad/webapp/errorlog.py (+14/-2) lib/canonical/launchpad/webapp/interfaces.py (+22/-10) lib/canonical/launchpad/webapp/tests/test_errorlog.py (+78/-2) lib/lp_sitecustomize.py (+31/-0) |
||||
To merge this branch: | bzr merge lp:~benji/launchpad/bug-697735 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+64535@code.launchpad.net |
Commit message
Description of the change
Bug 697735 is about OOPS reports that are generated when
zope.publisher.
?foo:int=7) and the conversion raises an exception. Generating an error
is fine, but we don't want OOPS reports logged when they occur.
This branch adds a marker interface that can be applied to an exception
that signals the OOPS reporting mechanism that no report should be
logged. Originally I added an underscore-prefixed attribute to
exceptions that shouldn't be recorded, but Gary suggested the
improvement of using a marker interface.
The several tests were added to
lib/canonical/
behavior.
A fair bit of mostly whitespace lint was fixed in
./lib/canonical
The only slightly suboptimal part of this branch is the need to monkey
patch zope.publisher.
could wrap the conversion functions with a try/except to mark
ValueErrors as non-oops-report-worthy. Gary and I discussed this
aspect of the branch and felt like it was a reasonable compromise.
Unmerged revisions
- 13204. By Benji York
-
freshen from devel
- 13203. By Benji York
-
freshen from devel
- 13202. By Benji York
-
fix lint
- 13201. By Benji York
-
checkpoint
- 13200. By Benji York
-
don't treat URL parameter type conversion failures as OOPS-worthy
Preview Diff
1 | === removed file 'database/schema/patch-2208-63-0.sql' | |||
2 | --- database/schema/patch-2208-63-0.sql 2011-06-05 07:13:43 +0000 | |||
3 | +++ database/schema/patch-2208-63-0.sql 1970-01-01 00:00:00 +0000 | |||
4 | @@ -1,597 +0,0 @@ | |||
5 | 1 | -- Copyright 2011 Canonical Ltd. This software is licensed under the | ||
6 | 2 | -- GNU Affero General Public License version 3 (see the file LICENSE). | ||
7 | 3 | |||
8 | 4 | SET client_min_messages=ERROR; | ||
9 | 5 | |||
10 | 6 | CREATE TABLE BugSummary( | ||
11 | 7 | -- Slony needs a primary key and there are no natural candidates. | ||
12 | 8 | id serial PRIMARY KEY, | ||
13 | 9 | count INTEGER NOT NULL default 0, | ||
14 | 10 | product INTEGER REFERENCES Product ON DELETE CASCADE, | ||
15 | 11 | productseries INTEGER REFERENCES ProductSeries ON DELETE CASCADE, | ||
16 | 12 | distribution INTEGER REFERENCES Distribution ON DELETE CASCADE, | ||
17 | 13 | distroseries INTEGER REFERENCES DistroSeries ON DELETE CASCADE, | ||
18 | 14 | sourcepackagename INTEGER REFERENCES SourcePackageName ON DELETE CASCADE, | ||
19 | 15 | viewed_by INTEGER, -- No REFERENCES because it is trigger maintained. | ||
20 | 16 | tag TEXT, | ||
21 | 17 | status INTEGER NOT NULL, | ||
22 | 18 | milestone INTEGER REFERENCES Milestone ON DELETE CASCADE, | ||
23 | 19 | CONSTRAINT bugtask_assignment_checks CHECK ( | ||
24 | 20 | CASE | ||
25 | 21 | WHEN product IS NOT NULL THEN | ||
26 | 22 | productseries IS NULL | ||
27 | 23 | AND distribution IS NULL | ||
28 | 24 | AND distroseries IS NULL | ||
29 | 25 | AND sourcepackagename IS NULL | ||
30 | 26 | WHEN productseries IS NOT NULL THEN | ||
31 | 27 | distribution IS NULL | ||
32 | 28 | AND distroseries IS NULL | ||
33 | 29 | AND sourcepackagename IS NULL | ||
34 | 30 | WHEN distribution IS NOT NULL THEN | ||
35 | 31 | distroseries IS NULL | ||
36 | 32 | WHEN distroseries IS NOT NULL THEN | ||
37 | 33 | TRUE | ||
38 | 34 | ELSE | ||
39 | 35 | FALSE | ||
40 | 36 | END) | ||
41 | 37 | ); | ||
42 | 38 | |||
43 | 39 | ---- Bulk load into the table - after this it is maintained by trigger. Timed | ||
44 | 40 | -- at 2-3 minutes on staging. | ||
45 | 41 | -- basic theory: each bug *task* has some unary dimensions (like status) and | ||
46 | 42 | -- some N-ary dimensions (like contexts [sourcepackage+distro, distro only], or | ||
47 | 43 | -- subscriptions, or tags). For N-ary dimensions we record the bug against all | ||
48 | 44 | -- positions in that dimension. | ||
49 | 45 | -- Some tasks aggregate into the same dimension - e.g. two different source | ||
50 | 46 | -- packages tasks in Ubuntu. At the time of writing we only want to count those | ||
51 | 47 | -- once ( because we have had user confusion when two tasks of the same bug are | ||
52 | 48 | -- both counted toward portal aggregates). So we add bug.id distinct. | ||
53 | 49 | -- We don't map INCOMPLETE to INCOMPLETE_WITH_RESPONSE - instead we'll let that | ||
54 | 50 | -- migration happen separately. | ||
55 | 51 | -- So the rules the code below should be implementing are: | ||
56 | 52 | -- once for each task in a different target | ||
57 | 53 | -- once for each subscription (private bugs) (left join subscribers conditionally on privacy) | ||
58 | 54 | -- once for each sourcepackage name + one with sourcepackagename=NULL (two queries unioned) | ||
59 | 55 | -- once for each tag + one with tag=NULL (two queries unioned) | ||
60 | 56 | -- bugs with duplicateof non null are excluded because we exclude them from all our aggregates. | ||
61 | 57 | INSERT INTO bugsummary ( | ||
62 | 58 | count, product, productseries, distribution, distroseries, | ||
63 | 59 | sourcepackagename, viewed_by, tag, status, milestone) | ||
64 | 60 | WITH | ||
65 | 61 | -- kill dupes | ||
66 | 62 | relevant_bug AS (SELECT * FROM bug where duplicateof is NULL), | ||
67 | 63 | |||
68 | 64 | -- (bug.id, tag) for all bug-tag pairs plus (bug.id, NULL) for all bugs | ||
69 | 65 | bug_tags AS ( | ||
70 | 66 | SELECT relevant_bug.id, NULL::text AS tag FROM relevant_bug | ||
71 | 67 | UNION | ||
72 | 68 | SELECT relevant_bug.id, tag | ||
73 | 69 | FROM relevant_bug INNER JOIN bugtag ON relevant_bug.id=bugtag.bug), | ||
74 | 70 | -- (bug.id, NULL) for all public bugs + (bug.id, viewer) for all | ||
75 | 71 | -- (subscribers+assignee) on private bugs | ||
76 | 72 | bug_viewers AS ( | ||
77 | 73 | SELECT relevant_bug.id, NULL::integer AS person | ||
78 | 74 | FROM relevant_bug WHERE NOT relevant_bug.private | ||
79 | 75 | UNION | ||
80 | 76 | SELECT relevant_bug.id, assignee AS person | ||
81 | 77 | FROM relevant_bug | ||
82 | 78 | INNER JOIN bugtask ON relevant_bug.id=bugtask.bug | ||
83 | 79 | WHERE relevant_bug.private and bugtask.assignee IS NOT NULL | ||
84 | 80 | UNION | ||
85 | 81 | SELECT relevant_bug.id, bugsubscription.person | ||
86 | 82 | FROM relevant_bug INNER JOIN bugsubscription | ||
87 | 83 | ON bugsubscription.bug=relevant_bug.id WHERE relevant_bug.private), | ||
88 | 84 | |||
89 | 85 | -- (bugtask.(bug, product, productseries, distribution, distroseries, | ||
90 | 86 | -- sourcepackagename, status, milestone) for all bugs + the same with | ||
91 | 87 | -- sourcepackage squashed to NULL) | ||
92 | 88 | tasks AS ( | ||
93 | 89 | SELECT | ||
94 | 90 | bug, product, productseries, distribution, distroseries, | ||
95 | 91 | sourcepackagename, status, milestone | ||
96 | 92 | FROM bugtask | ||
97 | 93 | UNION | ||
98 | 94 | SELECT DISTINCT ON ( | ||
99 | 95 | bug, product, productseries, distribution, distroseries, | ||
100 | 96 | sourcepackagename, milestone) | ||
101 | 97 | bug, product, productseries, distribution, distroseries, | ||
102 | 98 | NULL::integer as sourcepackagename, | ||
103 | 99 | status, milestone | ||
104 | 100 | FROM bugtask where sourcepackagename IS NOT NULL) | ||
105 | 101 | |||
106 | 102 | -- Now combine | ||
107 | 103 | SELECT | ||
108 | 104 | count(*), product, productseries, distribution, distroseries, | ||
109 | 105 | sourcepackagename, person, tag, status, milestone | ||
110 | 106 | FROM relevant_bug | ||
111 | 107 | INNER JOIN bug_tags ON relevant_bug.id=bug_tags.id | ||
112 | 108 | INNER JOIN bug_viewers ON relevant_bug.id=bug_viewers.id | ||
113 | 109 | INNER JOIN tasks on tasks.bug=relevant_bug.id | ||
114 | 110 | GROUP BY | ||
115 | 111 | product, productseries, distribution, distroseries, | ||
116 | 112 | sourcepackagename, person, tag, status, milestone; | ||
117 | 113 | |||
118 | 114 | -- Need indices for FK CASCADE DELETE to find any FK easily | ||
119 | 115 | CREATE INDEX bugsummary__distribution__idx ON BugSummary (distribution) | ||
120 | 116 | WHERE distribution IS NOT NULL; | ||
121 | 117 | |||
122 | 118 | CREATE INDEX bugsummary__distroseries__idx ON BugSummary (distroseries) | ||
123 | 119 | WHERE distroseries IS NOT NULL; | ||
124 | 120 | |||
125 | 121 | CREATE INDEX bugsummary__viewed_by__idx ON BugSummary (viewed_by) | ||
126 | 122 | WHERE viewed_by IS NOT NULL; | ||
127 | 123 | |||
128 | 124 | CREATE INDEX bugsummary__product__idx ON BugSummary (product) | ||
129 | 125 | WHERE product IS NOT NULL; | ||
130 | 126 | |||
131 | 127 | CREATE INDEX bugsummary__productseries__idx ON BugSummary (productseries) | ||
132 | 128 | WHERE productseries IS NOT NULL; | ||
133 | 129 | |||
134 | 130 | -- can only have one fact row per set of dimensions | ||
135 | 131 | CREATE UNIQUE INDEX bugsummary__dimensions__unique ON bugsummary ( | ||
136 | 132 | status, | ||
137 | 133 | COALESCE(product, (-1)), | ||
138 | 134 | COALESCE(productseries, (-1)), | ||
139 | 135 | COALESCE(distribution, (-1)), | ||
140 | 136 | COALESCE(distroseries, (-1)), | ||
141 | 137 | COALESCE(sourcepackagename, (-1)), | ||
142 | 138 | COALESCE(viewed_by, (-1)), | ||
143 | 139 | COALESCE(milestone, (-1)), | ||
144 | 140 | COALESCE(tag, (''))); | ||
145 | 141 | |||
146 | 142 | -- While querying is tolerably fast with the base dimension indices, | ||
147 | 143 | -- we want snappy: | ||
148 | 144 | -- Distribution bug counts | ||
149 | 145 | CREATE INDEX bugsummary__distribution_count__idx | ||
150 | 146 | ON BugSummary (distribution) | ||
151 | 147 | WHERE sourcepackagename IS NULL AND tag IS NULL; | ||
152 | 148 | |||
153 | 149 | -- Distribution wide tag counts | ||
154 | 150 | CREATE INDEX bugsummary__distribution_tag_count__idx | ||
155 | 151 | ON BugSummary (distribution) | ||
156 | 152 | WHERE sourcepackagename IS NULL AND tag IS NOT NULL; | ||
157 | 153 | |||
158 | 154 | -- Everything (counts) | ||
159 | 155 | CREATE INDEX bugsummary__status_count__idx | ||
160 | 156 | ON BugSummary (status) | ||
161 | 157 | WHERE sourcepackagename IS NULL AND tag IS NULL; | ||
162 | 158 | |||
163 | 159 | -- Everything (tags) | ||
164 | 160 | CREATE INDEX bugsummary__tag_count__idx | ||
165 | 161 | ON BugSummary (status) | ||
166 | 162 | WHERE sourcepackagename IS NULL AND tag IS NOT NULL; | ||
167 | 163 | |||
168 | 164 | |||
169 | 165 | -- | ||
170 | 166 | -- Functions exist here for pathalogical reasons. | ||
171 | 167 | -- | ||
172 | 168 | -- They can't go in trusted.sql at the moment, because trusted.sql is | ||
173 | 169 | -- run against an empty database. If these functions where in there, | ||
174 | 170 | -- it would fail because they use BugSummary table as a useful | ||
175 | 171 | -- composite type. | ||
176 | 172 | -- I suspect we will need to leave these function definitions in here, | ||
177 | 173 | -- and move them to trusted.sql after the baseline SQL script contains | ||
178 | 174 | -- the BugSummary table definition. | ||
179 | 175 | |||
180 | 176 | -- We also considered switching from one 'trusted.sql' to two files - | ||
181 | 177 | -- pre_patch.sql and post_patch.sql. But that doesn't gain us much | ||
182 | 178 | -- as the functions need to be declared before the triggers can be | ||
183 | 179 | -- created. It would work, but we would still need stub 'forward | ||
184 | 180 | -- declarations' of the functions in here, with the functions recreated | ||
185 | 181 | -- with the real implementation in post_patch.sql. | ||
186 | 182 | |||
187 | 183 | CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID | ||
188 | 184 | LANGUAGE plpgsql AS | ||
189 | 185 | $$ | ||
190 | 186 | BEGIN | ||
191 | 187 | -- Shameless adaption from postgresql manual | ||
192 | 188 | LOOP | ||
193 | 189 | -- first try to update the row | ||
194 | 190 | UPDATE BugSummary SET count = count + 1 | ||
195 | 191 | WHERE | ||
196 | 192 | product IS NOT DISTINCT FROM d.product | ||
197 | 193 | AND productseries IS NOT DISTINCT FROM d.productseries | ||
198 | 194 | AND distribution IS NOT DISTINCT FROM d.distribution | ||
199 | 195 | AND distroseries IS NOT DISTINCT FROM d.distroseries | ||
200 | 196 | AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename | ||
201 | 197 | AND viewed_by IS NOT DISTINCT FROM d.viewed_by | ||
202 | 198 | AND tag IS NOT DISTINCT FROM d.tag | ||
203 | 199 | AND status IS NOT DISTINCT FROM d.status | ||
204 | 200 | AND milestone IS NOT DISTINCT FROM d.milestone; | ||
205 | 201 | IF found THEN | ||
206 | 202 | RETURN; | ||
207 | 203 | END IF; | ||
208 | 204 | -- not there, so try to insert the key | ||
209 | 205 | -- if someone else inserts the same key concurrently, | ||
210 | 206 | -- we could get a unique-key failure | ||
211 | 207 | BEGIN | ||
212 | 208 | INSERT INTO BugSummary( | ||
213 | 209 | count, product, productseries, distribution, | ||
214 | 210 | distroseries, sourcepackagename, viewed_by, tag, | ||
215 | 211 | status, milestone) | ||
216 | 212 | VALUES ( | ||
217 | 213 | 1, d.product, d.productseries, d.distribution, | ||
218 | 214 | d.distroseries, d.sourcepackagename, d.viewed_by, d.tag, | ||
219 | 215 | d.status, d.milestone); | ||
220 | 216 | RETURN; | ||
221 | 217 | EXCEPTION WHEN unique_violation THEN | ||
222 | 218 | -- do nothing, and loop to try the UPDATE again | ||
223 | 219 | END; | ||
224 | 220 | END LOOP; | ||
225 | 221 | END; | ||
226 | 222 | $$; | ||
227 | 223 | |||
228 | 224 | COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS | ||
229 | 225 | 'UPSERT into bugsummary incrementing one row'; | ||
230 | 226 | |||
231 | 227 | CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID | ||
232 | 228 | LANGUAGE SQL AS | ||
233 | 229 | $$ | ||
234 | 230 | -- We own the row reference, so in the absence of bugs this cannot | ||
235 | 231 | -- fail - just decrement the row. | ||
236 | 232 | UPDATE BugSummary SET count = count - 1 | ||
237 | 233 | WHERE | ||
238 | 234 | product IS NOT DISTINCT FROM $1.product | ||
239 | 235 | AND productseries IS NOT DISTINCT FROM $1.productseries | ||
240 | 236 | AND distribution IS NOT DISTINCT FROM $1.distribution | ||
241 | 237 | AND distroseries IS NOT DISTINCT FROM $1.distroseries | ||
242 | 238 | AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename | ||
243 | 239 | AND viewed_by IS NOT DISTINCT FROM $1.viewed_by | ||
244 | 240 | AND tag IS NOT DISTINCT FROM $1.tag | ||
245 | 241 | AND status IS NOT DISTINCT FROM $1.status | ||
246 | 242 | AND milestone IS NOT DISTINCT FROM $1.milestone; | ||
247 | 243 | -- gc the row (perhaps should be garbo but easy enough to add here: | ||
248 | 244 | DELETE FROM bugsummary | ||
249 | 245 | WHERE | ||
250 | 246 | count=0 | ||
251 | 247 | AND product IS NOT DISTINCT FROM $1.product | ||
252 | 248 | AND productseries IS NOT DISTINCT FROM $1.productseries | ||
253 | 249 | AND distribution IS NOT DISTINCT FROM $1.distribution | ||
254 | 250 | AND distroseries IS NOT DISTINCT FROM $1.distroseries | ||
255 | 251 | AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename | ||
256 | 252 | AND viewed_by IS NOT DISTINCT FROM $1.viewed_by | ||
257 | 253 | AND tag IS NOT DISTINCT FROM $1.tag | ||
258 | 254 | AND status IS NOT DISTINCT FROM $1.status | ||
259 | 255 | AND milestone IS NOT DISTINCT FROM $1.milestone; | ||
260 | 256 | -- If its not found then someone else also dec'd and won concurrently. | ||
261 | 257 | $$; | ||
262 | 258 | |||
263 | 259 | COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS | ||
264 | 260 | 'UPSERT into bugsummary incrementing one row'; | ||
265 | 261 | |||
266 | 262 | |||
267 | 263 | CREATE OR REPLACE FUNCTION bug_row(bug_id integer) | ||
268 | 264 | RETURNS bug LANGUAGE SQL STABLE AS | ||
269 | 265 | $$ | ||
270 | 266 | SELECT * FROM Bug WHERE id=$1; | ||
271 | 267 | $$; | ||
272 | 268 | COMMENT ON FUNCTION bug_row(integer) IS | ||
273 | 269 | 'Helper for manually testing functions requiring a bug row as input. eg. SELECT * FROM bugsummary_tags(bug_row(1))'; | ||
274 | 270 | |||
275 | 271 | |||
276 | 272 | CREATE OR REPLACE FUNCTION bugsummary_viewers(BUG_ROW bug) | ||
277 | 273 | RETURNS SETOF bugsubscription LANGUAGE SQL STABLE AS | ||
278 | 274 | $$ | ||
279 | 275 | SELECT * | ||
280 | 276 | FROM BugSubscription | ||
281 | 277 | WHERE | ||
282 | 278 | bugsubscription.bug=$1.id | ||
283 | 279 | AND $1.private IS TRUE; | ||
284 | 280 | $$; | ||
285 | 281 | |||
286 | 282 | COMMENT ON FUNCTION bugsummary_viewers(bug) IS | ||
287 | 283 | 'Return (bug, viewer) for all viewers if private, nothing otherwise'; | ||
288 | 284 | |||
289 | 285 | |||
290 | 286 | CREATE OR REPLACE FUNCTION bugsummary_tags(BUG_ROW bug) | ||
291 | 287 | RETURNS SETOF bugtag LANGUAGE SQL STABLE AS | ||
292 | 288 | $$ | ||
293 | 289 | SELECT * FROM BugTag WHERE BugTag.bug = $1.id | ||
294 | 290 | UNION ALL | ||
295 | 291 | SELECT NULL::integer, $1.id, NULL::text; | ||
296 | 292 | $$; | ||
297 | 293 | |||
298 | 294 | COMMENT ON FUNCTION bugsummary_tags(bug) IS | ||
299 | 295 | 'Return (bug, tag) for all tags + (bug, NULL::text)'; | ||
300 | 296 | |||
301 | 297 | |||
302 | 298 | CREATE OR REPLACE FUNCTION bugsummary_tasks(BUG_ROW bug) | ||
303 | 299 | RETURNS SETOF bugtask LANGUAGE plpgsql STABLE AS | ||
304 | 300 | $$ | ||
305 | 301 | DECLARE | ||
306 | 302 | bt bugtask%ROWTYPE; | ||
307 | 303 | r record; | ||
308 | 304 | BEGIN | ||
309 | 305 | bt.bug = BUG_ROW.id; | ||
310 | 306 | |||
311 | 307 | -- One row only for each target permutation - need to ignore other fields | ||
312 | 308 | -- like date last modified to deal with conjoined masters and multiple | ||
313 | 309 | -- sourcepackage tasks in a distro. | ||
314 | 310 | FOR r IN | ||
315 | 311 | SELECT | ||
316 | 312 | product, productseries, distribution, distroseries, | ||
317 | 313 | sourcepackagename, status, milestone | ||
318 | 314 | FROM BugTask WHERE bug=BUG_ROW.id | ||
319 | 315 | UNION | ||
320 | 316 | SELECT | ||
321 | 317 | product, productseries, distribution, distroseries, | ||
322 | 318 | NULL, status, milestone | ||
323 | 319 | FROM BugTask WHERE bug=BUG_ROW.id AND sourcepackagename IS NOT NULL | ||
324 | 320 | LOOP | ||
325 | 321 | bt.product = r.product; | ||
326 | 322 | bt.productseries = r.productseries; | ||
327 | 323 | bt.distribution = r.distribution; | ||
328 | 324 | bt.distroseries = r.distroseries; | ||
329 | 325 | bt.sourcepackagename = r.sourcepackagename; | ||
330 | 326 | bt.status = r.status; | ||
331 | 327 | bt.milestone = r.milestone; | ||
332 | 328 | RETURN NEXT bt; | ||
333 | 329 | END LOOP; | ||
334 | 330 | END; | ||
335 | 331 | $$; | ||
336 | 332 | |||
337 | 333 | COMMENT ON FUNCTION bugsummary_tasks(bug) IS | ||
338 | 334 | 'Return all tasks for the bug + all sourcepackagename tasks again with the sourcepackagename squashed'; | ||
339 | 335 | |||
340 | 336 | |||
341 | 337 | CREATE OR REPLACE FUNCTION bugsummary_locations(BUG_ROW bug) | ||
342 | 338 | RETURNS SETOF bugsummary LANGUAGE plpgsql AS | ||
343 | 339 | $$ | ||
344 | 340 | BEGIN | ||
345 | 341 | IF BUG_ROW.duplicateof IS NOT NULL THEN | ||
346 | 342 | RETURN; | ||
347 | 343 | END IF; | ||
348 | 344 | RETURN QUERY | ||
349 | 345 | SELECT | ||
350 | 346 | CAST(NULL AS integer) AS id, | ||
351 | 347 | CAST(1 AS integer) AS count, | ||
352 | 348 | product, productseries, distribution, distroseries, | ||
353 | 349 | sourcepackagename, person AS viewed_by, tag, status, milestone | ||
354 | 350 | FROM bugsummary_tasks(BUG_ROW) AS tasks | ||
355 | 351 | JOIN bugsummary_tags(BUG_ROW) AS bug_tags ON TRUE | ||
356 | 352 | LEFT OUTER JOIN bugsummary_viewers(BUG_ROW) AS bug_viewers ON TRUE; | ||
357 | 353 | END; | ||
358 | 354 | $$; | ||
359 | 355 | |||
360 | 356 | COMMENT ON FUNCTION bugsummary_locations(bug) IS | ||
361 | 357 | 'Calculate what BugSummary rows should exist for a given Bug.'; | ||
362 | 358 | |||
363 | 359 | |||
364 | 360 | CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID | ||
365 | 361 | LANGUAGE plpgsql VOLATILE AS | ||
366 | 362 | $$ | ||
367 | 363 | DECLARE | ||
368 | 364 | d bugsummary%ROWTYPE; | ||
369 | 365 | BEGIN | ||
370 | 366 | -- Grab a suitable lock before we start calculating bug summary data | ||
371 | 367 | -- to avoid race conditions. This lock allows SELECT but blocks writes. | ||
372 | 368 | LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE; | ||
373 | 369 | FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP | ||
374 | 370 | PERFORM bug_summary_inc(d); | ||
375 | 371 | END LOOP; | ||
376 | 372 | END; | ||
377 | 373 | $$; | ||
378 | 374 | |||
379 | 375 | COMMENT ON FUNCTION summarise_bug(bug) IS | ||
380 | 376 | 'AFTER summarise a bug row into bugsummary.'; | ||
381 | 377 | |||
382 | 378 | |||
383 | 379 | CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID | ||
384 | 380 | LANGUAGE plpgsql VOLATILE AS | ||
385 | 381 | $$ | ||
386 | 382 | DECLARE | ||
387 | 383 | d bugsummary%ROWTYPE; | ||
388 | 384 | BEGIN | ||
389 | 385 | -- Grab a suitable lock before we start calculating bug summary data | ||
390 | 386 | -- to avoid race conditions. This lock allows SELECT but blocks writes. | ||
391 | 387 | LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE; | ||
392 | 388 | FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP | ||
393 | 389 | PERFORM bug_summary_dec(d); | ||
394 | 390 | END LOOP; | ||
395 | 391 | END; | ||
396 | 392 | $$; | ||
397 | 393 | |||
398 | 394 | COMMENT ON FUNCTION unsummarise_bug(bug) IS | ||
399 | 395 | 'AFTER unsummarise a bug row from bugsummary.'; | ||
400 | 396 | |||
401 | 397 | |||
402 | 398 | CREATE OR REPLACE FUNCTION bug_maintain_bug_summary() RETURNS TRIGGER | ||
403 | 399 | LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS | ||
404 | 400 | $$ | ||
405 | 401 | BEGIN | ||
406 | 402 | -- There is no INSERT logic, as a bug will not have any summary | ||
407 | 403 | -- information until BugTask rows have been attached. | ||
408 | 404 | IF TG_OP = 'UPDATE' THEN | ||
409 | 405 | IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof | ||
410 | 406 | OR OLD.private IS DISTINCT FROM NEW.private THEN | ||
411 | 407 | PERFORM unsummarise_bug(OLD); | ||
412 | 408 | PERFORM summarise_bug(NEW); | ||
413 | 409 | END IF; | ||
414 | 410 | |||
415 | 411 | ELSIF TG_OP = 'DELETE' THEN | ||
416 | 412 | PERFORM unsummarise_bug(OLD); | ||
417 | 413 | END IF; | ||
418 | 414 | |||
419 | 415 | RETURN NULL; -- Ignored - this is an AFTER trigger | ||
420 | 416 | END; | ||
421 | 417 | $$; | ||
422 | 418 | |||
423 | 419 | COMMENT ON FUNCTION bug_maintain_bug_summary() IS | ||
424 | 420 | 'AFTER trigger on bug maintaining the bugs summaries in bugsummary.'; | ||
425 | 421 | |||
426 | 422 | |||
427 | 423 | CREATE OR REPLACE FUNCTION bugtask_maintain_bug_summary() RETURNS TRIGGER | ||
428 | 424 | LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS | ||
429 | 425 | $$ | ||
430 | 426 | BEGIN | ||
431 | 427 | -- This trigger only works if we are inserting, updating or deleting | ||
432 | 428 | -- a single row per statement. | ||
433 | 429 | |||
434 | 430 | -- Unlike bug_maintain_bug_summary, this trigger does not have access | ||
435 | 431 | -- to the old bug when invoked as an AFTER trigger. To work around this | ||
436 | 432 | -- we install this trigger as both a BEFORE and an AFTER trigger. | ||
437 | 433 | IF TG_OP = 'INSERT' THEN | ||
438 | 434 | IF TG_WHEN = 'BEFORE' THEN | ||
439 | 435 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
440 | 436 | ELSE | ||
441 | 437 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
442 | 438 | END IF; | ||
443 | 439 | RETURN NEW; | ||
444 | 440 | |||
445 | 441 | ELSIF TG_OP = 'DELETE' THEN | ||
446 | 442 | IF TG_WHEN = 'BEFORE' THEN | ||
447 | 443 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
448 | 444 | ELSE | ||
449 | 445 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
450 | 446 | END IF; | ||
451 | 447 | RETURN OLD; | ||
452 | 448 | |||
453 | 449 | ELSE | ||
454 | 450 | IF (OLD.product IS DISTINCT FROM NEW.product | ||
455 | 451 | OR OLD.productseries IS DISTINCT FROM NEW.productseries | ||
456 | 452 | OR OLD.distribution IS DISTINCT FROM NEW.distribution | ||
457 | 453 | OR OLD.distroseries IS DISTINCT FROM NEW.distroseries | ||
458 | 454 | OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename | ||
459 | 455 | OR OLD.status IS DISTINCT FROM NEW.status | ||
460 | 456 | OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN | ||
461 | 457 | IF TG_WHEN = 'BEFORE' THEN | ||
462 | 458 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
463 | 459 | IF OLD.bug <> NEW.bug THEN | ||
464 | 460 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
465 | 461 | END IF; | ||
466 | 462 | ELSE | ||
467 | 463 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
468 | 464 | IF OLD.bug <> NEW.bug THEN | ||
469 | 465 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
470 | 466 | END IF; | ||
471 | 467 | END IF; | ||
472 | 468 | END IF; | ||
473 | 469 | RETURN NEW; | ||
474 | 470 | END IF; | ||
475 | 471 | END; | ||
476 | 472 | $$; | ||
477 | 473 | |||
478 | 474 | COMMENT ON FUNCTION bugtask_maintain_bug_summary() IS | ||
479 | 475 | 'Both BEFORE & AFTER trigger on bugtask maintaining the bugs summaries in bugsummary.'; | ||
480 | 476 | |||
481 | 477 | |||
482 | 478 | CREATE OR REPLACE FUNCTION bugsubscription_maintain_bug_summary() | ||
483 | 479 | RETURNS TRIGGER LANGUAGE plpgsql VOLATILE | ||
484 | 480 | SECURITY DEFINER SET search_path TO public AS | ||
485 | 481 | $$ | ||
486 | 482 | BEGIN | ||
487 | 483 | -- This trigger only works if we are inserting, updating or deleting | ||
488 | 484 | -- a single row per statement. | ||
489 | 485 | IF TG_OP = 'INSERT' THEN | ||
490 | 486 | IF TG_WHEN = 'BEFORE' THEN | ||
491 | 487 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
492 | 488 | ELSE | ||
493 | 489 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
494 | 490 | END IF; | ||
495 | 491 | RETURN NEW; | ||
496 | 492 | ELSIF TG_OP = 'DELETE' THEN | ||
497 | 493 | IF TG_WHEN = 'BEFORE' THEN | ||
498 | 494 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
499 | 495 | ELSE | ||
500 | 496 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
501 | 497 | END IF; | ||
502 | 498 | RETURN OLD; | ||
503 | 499 | ELSE | ||
504 | 500 | IF (OLD.person IS DISTINCT FROM NEW.person | ||
505 | 501 | OR OLD.bug IS DISTINCT FROM NEW.bug) THEN | ||
506 | 502 | IF TG_WHEN = 'BEFORE' THEN | ||
507 | 503 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
508 | 504 | IF OLD.bug <> NEW.bug THEN | ||
509 | 505 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
510 | 506 | END IF; | ||
511 | 507 | ELSE | ||
512 | 508 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
513 | 509 | IF OLD.bug <> NEW.bug THEN | ||
514 | 510 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
515 | 511 | END IF; | ||
516 | 512 | END IF; | ||
517 | 513 | END IF; | ||
518 | 514 | RETURN NEW; | ||
519 | 515 | END IF; | ||
520 | 516 | END; | ||
521 | 517 | $$; | ||
522 | 518 | |||
523 | 519 | COMMENT ON FUNCTION bugsubscription_maintain_bug_summary() IS | ||
524 | 520 | 'AFTER trigger on bugsubscription maintaining the bugs summaries in bugsummary.'; | ||
525 | 521 | |||
526 | 522 | |||
527 | 523 | CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() RETURNS TRIGGER | ||
528 | 524 | LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS | ||
529 | 525 | $$ | ||
530 | 526 | BEGIN | ||
531 | 527 | IF TG_OP = 'INSERT' THEN | ||
532 | 528 | IF TG_WHEN = 'BEFORE' THEN | ||
533 | 529 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
534 | 530 | ELSE | ||
535 | 531 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
536 | 532 | END IF; | ||
537 | 533 | RETURN NEW; | ||
538 | 534 | ELSIF TG_OP = 'DELETE' THEN | ||
539 | 535 | IF TG_WHEN = 'BEFORE' THEN | ||
540 | 536 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
541 | 537 | ELSE | ||
542 | 538 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
543 | 539 | END IF; | ||
544 | 540 | RETURN OLD; | ||
545 | 541 | ELSE | ||
546 | 542 | IF TG_WHEN = 'BEFORE' THEN | ||
547 | 543 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
548 | 544 | IF OLD.bug <> NEW.bug THEN | ||
549 | 545 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
550 | 546 | END IF; | ||
551 | 547 | ELSE | ||
552 | 548 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
553 | 549 | IF OLD.bug <> NEW.bug THEN | ||
554 | 550 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
555 | 551 | END IF; | ||
556 | 552 | END IF; | ||
557 | 553 | RETURN NEW; | ||
558 | 554 | END IF; | ||
559 | 555 | END; | ||
560 | 556 | $$; | ||
561 | 557 | |||
562 | 558 | COMMENT ON FUNCTION bugtag_maintain_bug_summary() IS | ||
563 | 559 | 'AFTER trigger on bugtag maintaining the bugs summaries in bugsummary.'; | ||
564 | 560 | |||
565 | 561 | |||
566 | 562 | -- we need to maintain the summaries when things change. Each variable the | ||
567 | 563 | -- population script above uses needs to be accounted for. | ||
568 | 564 | |||
569 | 565 | -- bug: duplicateof, private (not INSERT because a task is needed to be included in summaries. | ||
570 | 566 | CREATE TRIGGER bug_maintain_bug_summary_trigger | ||
571 | 567 | AFTER UPDATE OR DELETE ON bug | ||
572 | 568 | FOR EACH ROW EXECUTE PROCEDURE bug_maintain_bug_summary(); | ||
573 | 569 | |||
574 | 570 | -- bugtask: target, status, milestone | ||
575 | 571 | CREATE TRIGGER bugtask_maintain_bug_summary_before_trigger | ||
576 | 572 | BEFORE INSERT OR UPDATE OR DELETE ON bugtask | ||
577 | 573 | FOR EACH ROW EXECUTE PROCEDURE bugtask_maintain_bug_summary(); | ||
578 | 574 | |||
579 | 575 | CREATE TRIGGER bugtask_maintain_bug_summary_after_trigger | ||
580 | 576 | AFTER INSERT OR UPDATE OR DELETE ON bugtask | ||
581 | 577 | FOR EACH ROW EXECUTE PROCEDURE bugtask_maintain_bug_summary(); | ||
582 | 578 | |||
583 | 579 | -- bugsubscription: existence | ||
584 | 580 | CREATE TRIGGER bugsubscription_maintain_bug_summary_before_trigger | ||
585 | 581 | BEFORE INSERT OR UPDATE OR DELETE ON bugsubscription | ||
586 | 582 | FOR EACH ROW EXECUTE PROCEDURE bugsubscription_maintain_bug_summary(); | ||
587 | 583 | |||
588 | 584 | CREATE TRIGGER bugsubscription_maintain_bug_summary_after_trigger | ||
589 | 585 | AFTER INSERT OR UPDATE OR DELETE ON bugsubscription | ||
590 | 586 | FOR EACH ROW EXECUTE PROCEDURE bugsubscription_maintain_bug_summary(); | ||
591 | 587 | |||
592 | 588 | -- bugtag: existence | ||
593 | 589 | CREATE TRIGGER bugtag_maintain_bug_summary_before_trigger | ||
594 | 590 | BEFORE INSERT OR UPDATE OR DELETE ON bugtag | ||
595 | 591 | FOR EACH ROW EXECUTE PROCEDURE bugtag_maintain_bug_summary(); | ||
596 | 592 | |||
597 | 593 | CREATE TRIGGER bugtag_maintain_bug_summary_after_trigger | ||
598 | 594 | AFTER INSERT OR UPDATE OR DELETE ON bugtag | ||
599 | 595 | FOR EACH ROW EXECUTE PROCEDURE bugtag_maintain_bug_summary(); | ||
600 | 596 | |||
601 | 597 | INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 0); | ||
602 | 598 | 0 | ||
603 | === removed file 'database/schema/patch-2208-63-1.sql' | |||
604 | --- database/schema/patch-2208-63-1.sql 2011-06-09 12:55:22 +0000 | |||
605 | +++ database/schema/patch-2208-63-1.sql 1970-01-01 00:00:00 +0000 | |||
606 | @@ -1,460 +0,0 @@ | |||
607 | 1 | -- Copyright 2011 Canonical Ltd. This software is licensed under the | ||
608 | 2 | -- GNU Affero General Public License version 3 (see the file LICENSE). | ||
609 | 3 | |||
610 | 4 | SET client_min_messages=ERROR; | ||
611 | 5 | |||
612 | 6 | CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID | ||
613 | 7 | LANGUAGE plpgsql AS | ||
614 | 8 | $$ | ||
615 | 9 | BEGIN | ||
616 | 10 | -- Shameless adaption from postgresql manual | ||
617 | 11 | LOOP | ||
618 | 12 | -- first try to update the row | ||
619 | 13 | UPDATE BugSummary SET count = count + 1 | ||
620 | 14 | WHERE | ||
621 | 15 | ((d.product IS NULL AND product IS NULL) | ||
622 | 16 | OR product = d.product) | ||
623 | 17 | AND ((d.productseries IS NULL AND productseries IS NULL) | ||
624 | 18 | OR productseries = d.productseries) | ||
625 | 19 | AND ((d.distribution IS NULL AND distribution IS NULL) | ||
626 | 20 | OR distribution = d.distribution) | ||
627 | 21 | AND ((d.distroseries IS NULL AND distroseries IS NULL) | ||
628 | 22 | OR distroseries = d.distroseries) | ||
629 | 23 | AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL) | ||
630 | 24 | OR sourcepackagename = d.sourcepackagename) | ||
631 | 25 | AND ((d.viewed_by IS NULL AND viewed_by IS NULL) | ||
632 | 26 | OR viewed_by = d.viewed_by) | ||
633 | 27 | AND ((d.tag IS NULL AND tag IS NULL) | ||
634 | 28 | OR tag = d.tag) | ||
635 | 29 | AND ((d.status IS NULL AND status IS NULL) | ||
636 | 30 | OR status = d.status) | ||
637 | 31 | AND ((d.milestone IS NULL AND milestone IS NULL) | ||
638 | 32 | OR milestone = d.milestone); | ||
639 | 33 | IF found THEN | ||
640 | 34 | RETURN; | ||
641 | 35 | END IF; | ||
642 | 36 | -- not there, so try to insert the key | ||
643 | 37 | -- if someone else inserts the same key concurrently, | ||
644 | 38 | -- we could get a unique-key failure | ||
645 | 39 | BEGIN | ||
646 | 40 | INSERT INTO BugSummary( | ||
647 | 41 | count, product, productseries, distribution, | ||
648 | 42 | distroseries, sourcepackagename, viewed_by, tag, | ||
649 | 43 | status, milestone) | ||
650 | 44 | VALUES ( | ||
651 | 45 | 1, d.product, d.productseries, d.distribution, | ||
652 | 46 | d.distroseries, d.sourcepackagename, d.viewed_by, d.tag, | ||
653 | 47 | d.status, d.milestone); | ||
654 | 48 | RETURN; | ||
655 | 49 | EXCEPTION WHEN unique_violation THEN | ||
656 | 50 | -- do nothing, and loop to try the UPDATE again | ||
657 | 51 | END; | ||
658 | 52 | END LOOP; | ||
659 | 53 | END; | ||
660 | 54 | $$; | ||
661 | 55 | |||
662 | 56 | COMMENT ON FUNCTION bug_summary_inc(bugsummary) IS | ||
663 | 57 | 'UPSERT into bugsummary incrementing one row'; | ||
664 | 58 | |||
665 | 59 | CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID | ||
666 | 60 | LANGUAGE SQL AS | ||
667 | 61 | $$ | ||
668 | 62 | -- We own the row reference, so in the absence of bugs this cannot | ||
669 | 63 | -- fail - just decrement the row. | ||
670 | 64 | UPDATE BugSummary SET count = count - 1 | ||
671 | 65 | WHERE | ||
672 | 66 | (($1.product IS NULL AND product IS NULL) | ||
673 | 67 | OR product = $1.product) | ||
674 | 68 | AND (($1.productseries IS NULL AND productseries IS NULL) | ||
675 | 69 | OR productseries = $1.productseries) | ||
676 | 70 | AND (($1.distribution IS NULL AND distribution IS NULL) | ||
677 | 71 | OR distribution = $1.distribution) | ||
678 | 72 | AND (($1.distroseries IS NULL AND distroseries IS NULL) | ||
679 | 73 | OR distroseries = $1.distroseries) | ||
680 | 74 | AND (($1.sourcepackagename IS NULL AND sourcepackagename IS NULL) | ||
681 | 75 | OR sourcepackagename = $1.sourcepackagename) | ||
682 | 76 | AND (($1.viewed_by IS NULL AND viewed_by IS NULL) | ||
683 | 77 | OR viewed_by = $1.viewed_by) | ||
684 | 78 | AND (($1.tag IS NULL AND tag IS NULL) | ||
685 | 79 | OR tag = $1.tag) | ||
686 | 80 | AND (($1.status IS NULL AND status IS NULL) | ||
687 | 81 | OR status = $1.status) | ||
688 | 82 | AND (($1.milestone IS NULL AND milestone IS NULL) | ||
689 | 83 | OR milestone = $1.milestone); | ||
690 | 84 | -- gc the row (perhaps should be garbo but easy enough to add here: | ||
691 | 85 | DELETE FROM bugsummary | ||
692 | 86 | WHERE | ||
693 | 87 | count=0 | ||
694 | 88 | AND (($1.product IS NULL AND product IS NULL) | ||
695 | 89 | OR product = $1.product) | ||
696 | 90 | AND (($1.productseries IS NULL AND productseries IS NULL) | ||
697 | 91 | OR productseries = $1.productseries) | ||
698 | 92 | AND (($1.distribution IS NULL AND distribution IS NULL) | ||
699 | 93 | OR distribution = $1.distribution) | ||
700 | 94 | AND (($1.distroseries IS NULL AND distroseries IS NULL) | ||
701 | 95 | OR distroseries = $1.distroseries) | ||
702 | 96 | AND (($1.sourcepackagename IS NULL AND sourcepackagename IS NULL) | ||
703 | 97 | OR sourcepackagename = $1.sourcepackagename) | ||
704 | 98 | AND (($1.viewed_by IS NULL AND viewed_by IS NULL) | ||
705 | 99 | OR viewed_by = $1.viewed_by) | ||
706 | 100 | AND (($1.tag IS NULL AND tag IS NULL) | ||
707 | 101 | OR tag = $1.tag) | ||
708 | 102 | AND (($1.status IS NULL AND status IS NULL) | ||
709 | 103 | OR status = $1.status) | ||
710 | 104 | AND (($1.milestone IS NULL AND milestone IS NULL) | ||
711 | 105 | OR milestone = $1.milestone); | ||
712 | 106 | -- If its not found then someone else also dec'd and won concurrently. | ||
713 | 107 | $$; | ||
714 | 108 | |||
715 | 109 | |||
716 | 110 | -- bad comment fixup | ||
717 | 111 | COMMENT ON FUNCTION bug_summary_dec(bugsummary) IS | ||
718 | 112 | 'UPSERT into bugsummary incrementing one row'; | ||
719 | 113 | |||
720 | 114 | CREATE OR REPLACE FUNCTION ensure_bugsummary_temp_journal() RETURNS VOID | ||
721 | 115 | LANGUAGE plpgsql VOLATILE AS | ||
722 | 116 | $$ | ||
723 | 117 | DECLARE | ||
724 | 118 | BEGIN | ||
725 | 119 | CREATE TEMPORARY TABLE bugsummary_temp_journal ( | ||
726 | 120 | LIKE bugsummary ) ON COMMIT DROP; | ||
727 | 121 | ALTER TABLE bugsummary_temp_journal ALTER COLUMN id DROP NOT NULL; | ||
728 | 122 | -- For safety use a unique index. | ||
729 | 123 | CREATE UNIQUE INDEX bugsummary__temp_journal__dimensions__unique ON bugsummary_temp_journal ( | ||
730 | 124 | status, | ||
731 | 125 | COALESCE(product, (-1)), | ||
732 | 126 | COALESCE(productseries, (-1)), | ||
733 | 127 | COALESCE(distribution, (-1)), | ||
734 | 128 | COALESCE(distroseries, (-1)), | ||
735 | 129 | COALESCE(sourcepackagename, (-1)), | ||
736 | 130 | COALESCE(viewed_by, (-1)), | ||
737 | 131 | COALESCE(milestone, (-1)), | ||
738 | 132 | COALESCE(tag, (''))); | ||
739 | 133 | EXCEPTION | ||
740 | 134 | WHEN duplicate_table THEN | ||
741 | 135 | NULL; | ||
742 | 136 | END; | ||
743 | 137 | $$; | ||
744 | 138 | |||
745 | 139 | COMMENT ON FUNCTION ensure_bugsummary_temp_journal() IS | ||
746 | 140 | 'Create a temporary table bugsummary_temp_journal if it does not exist.'; | ||
747 | 141 | |||
748 | 142 | |||
749 | 143 | CREATE OR REPLACE FUNCTION bug_summary_temp_journal_dec(d bugsummary) RETURNS VOID | ||
750 | 144 | LANGUAGE plpgsql AS | ||
751 | 145 | $$ | ||
752 | 146 | BEGIN | ||
753 | 147 | -- We own the row reference, so in the absence of bugs this cannot | ||
754 | 148 | -- fail - just decrement the row. | ||
755 | 149 | UPDATE BugSummary_Temp_Journal SET count = count - 1 | ||
756 | 150 | WHERE | ||
757 | 151 | ((d.product IS NULL AND product IS NULL) | ||
758 | 152 | OR product = d.product) | ||
759 | 153 | AND ((d.productseries IS NULL AND productseries IS NULL) | ||
760 | 154 | OR productseries = d.productseries) | ||
761 | 155 | AND ((d.distribution IS NULL AND distribution IS NULL) | ||
762 | 156 | OR distribution = d.distribution) | ||
763 | 157 | AND ((d.distroseries IS NULL AND distroseries IS NULL) | ||
764 | 158 | OR distroseries = d.distroseries) | ||
765 | 159 | AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL) | ||
766 | 160 | OR sourcepackagename = d.sourcepackagename) | ||
767 | 161 | AND ((d.viewed_by IS NULL AND viewed_by IS NULL) | ||
768 | 162 | OR viewed_by = d.viewed_by) | ||
769 | 163 | AND ((d.tag IS NULL AND tag IS NULL) | ||
770 | 164 | OR tag = d.tag) | ||
771 | 165 | AND ((d.status IS NULL AND status IS NULL) | ||
772 | 166 | OR status = d.status) | ||
773 | 167 | AND ((d.milestone IS NULL AND milestone IS NULL) | ||
774 | 168 | OR milestone = d.milestone); | ||
775 | 169 | IF found THEN | ||
776 | 170 | RETURN; | ||
777 | 171 | END IF; | ||
778 | 172 | -- not there, so try to insert the key | ||
779 | 173 | INSERT INTO BugSummary_Temp_Journal( | ||
780 | 174 | count, product, productseries, distribution, | ||
781 | 175 | distroseries, sourcepackagename, viewed_by, tag, | ||
782 | 176 | status, milestone) | ||
783 | 177 | VALUES ( | ||
784 | 178 | -1, d.product, d.productseries, d.distribution, | ||
785 | 179 | d.distroseries, d.sourcepackagename, d.viewed_by, d.tag, | ||
786 | 180 | d.status, d.milestone); | ||
787 | 181 | RETURN; | ||
788 | 182 | END; | ||
789 | 183 | $$; | ||
790 | 184 | |||
791 | 185 | COMMENT ON FUNCTION bug_summary_temp_journal_dec(bugsummary) IS | ||
792 | 186 | 'UPSERT into bugsummary_temp_journal decrementing one row'; | ||
793 | 187 | |||
794 | 188 | CREATE OR REPLACE FUNCTION bug_summary_temp_journal_inc(d bugsummary) RETURNS VOID | ||
795 | 189 | LANGUAGE plpgsql AS | ||
796 | 190 | $$ | ||
797 | 191 | BEGIN | ||
798 | 192 | -- first try to update the row | ||
799 | 193 | UPDATE BugSummary_Temp_Journal SET count = count + 1 | ||
800 | 194 | WHERE | ||
801 | 195 | ((d.product IS NULL AND product IS NULL) | ||
802 | 196 | OR product = d.product) | ||
803 | 197 | AND ((d.productseries IS NULL AND productseries IS NULL) | ||
804 | 198 | OR productseries = d.productseries) | ||
805 | 199 | AND ((d.distribution IS NULL AND distribution IS NULL) | ||
806 | 200 | OR distribution = d.distribution) | ||
807 | 201 | AND ((d.distroseries IS NULL AND distroseries IS NULL) | ||
808 | 202 | OR distroseries = d.distroseries) | ||
809 | 203 | AND ((d.sourcepackagename IS NULL AND sourcepackagename IS NULL) | ||
810 | 204 | OR sourcepackagename = d.sourcepackagename) | ||
811 | 205 | AND ((d.viewed_by IS NULL AND viewed_by IS NULL) | ||
812 | 206 | OR viewed_by = d.viewed_by) | ||
813 | 207 | AND ((d.tag IS NULL AND tag IS NULL) | ||
814 | 208 | OR tag = d.tag) | ||
815 | 209 | AND ((d.status IS NULL AND status IS NULL) | ||
816 | 210 | OR status = d.status) | ||
817 | 211 | AND ((d.milestone IS NULL AND milestone IS NULL) | ||
818 | 212 | OR milestone = d.milestone); | ||
819 | 213 | IF found THEN | ||
820 | 214 | RETURN; | ||
821 | 215 | END IF; | ||
822 | 216 | -- not there, so try to insert the key | ||
823 | 217 | INSERT INTO BugSummary_Temp_Journal( | ||
824 | 218 | count, product, productseries, distribution, | ||
825 | 219 | distroseries, sourcepackagename, viewed_by, tag, | ||
826 | 220 | status, milestone) | ||
827 | 221 | VALUES ( | ||
828 | 222 | 1, d.product, d.productseries, d.distribution, | ||
829 | 223 | d.distroseries, d.sourcepackagename, d.viewed_by, d.tag, | ||
830 | 224 | d.status, d.milestone); | ||
831 | 225 | RETURN; | ||
832 | 226 | END; | ||
833 | 227 | $$; | ||
834 | 228 | |||
835 | 229 | COMMENT ON FUNCTION bug_summary_temp_journal_inc(bugsummary) IS | ||
836 | 230 | 'UPSERT into bugsummary incrementing one row'; | ||
837 | 231 | |||
838 | 232 | CREATE OR REPLACE FUNCTION bug_summary_flush_temp_journal() RETURNS VOID | ||
839 | 233 | LANGUAGE plpgsql VOLATILE AS | ||
840 | 234 | $$ | ||
841 | 235 | DECLARE | ||
842 | 236 | d bugsummary%ROWTYPE; | ||
843 | 237 | BEGIN | ||
844 | 238 | -- may get called even though no summaries were made (for simplicity in the | ||
845 | 239 | -- callers) | ||
846 | 240 | PERFORM ensure_bugsummary_temp_journal(); | ||
847 | 241 | FOR d IN SELECT * FROM bugsummary_temp_journal LOOP | ||
848 | 242 | IF d.count < 0 THEN | ||
849 | 243 | PERFORM bug_summary_dec(d); | ||
850 | 244 | ELSIF d.count > 0 THEN | ||
851 | 245 | PERFORM bug_summary_inc(d); | ||
852 | 246 | END IF; | ||
853 | 247 | END LOOP; | ||
854 | 248 | DELETE FROM bugsummary_temp_journal; | ||
855 | 249 | END; | ||
856 | 250 | $$; | ||
857 | 251 | |||
858 | 252 | COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS | ||
859 | 253 | 'flush the temporary bugsummary journal into the bugsummary table'; | ||
860 | 254 | |||
861 | 255 | CREATE OR REPLACE FUNCTION unsummarise_bug(BUG_ROW bug) RETURNS VOID | ||
862 | 256 | LANGUAGE plpgsql VOLATILE AS | ||
863 | 257 | $$ | ||
864 | 258 | DECLARE | ||
865 | 259 | d bugsummary%ROWTYPE; | ||
866 | 260 | BEGIN | ||
867 | 261 | PERFORM ensure_bugsummary_temp_journal(); | ||
868 | 262 | FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP | ||
869 | 263 | PERFORM bug_summary_temp_journal_dec(d); | ||
870 | 264 | END LOOP; | ||
871 | 265 | END; | ||
872 | 266 | $$; | ||
873 | 267 | |||
874 | 268 | CREATE OR REPLACE FUNCTION summarise_bug(BUG_ROW bug) RETURNS VOID | ||
875 | 269 | LANGUAGE plpgsql VOLATILE AS | ||
876 | 270 | $$ | ||
877 | 271 | DECLARE | ||
878 | 272 | d bugsummary%ROWTYPE; | ||
879 | 273 | BEGIN | ||
880 | 274 | PERFORM ensure_bugsummary_temp_journal(); | ||
881 | 275 | FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP | ||
882 | 276 | PERFORM bug_summary_temp_journal_inc(d); | ||
883 | 277 | END LOOP; | ||
884 | 278 | END; | ||
885 | 279 | $$; | ||
886 | 280 | |||
887 | 281 | -- fixed to summarise less often and use the journal. | ||
888 | 282 | CREATE OR REPLACE FUNCTION bugsubscription_maintain_bug_summary() | ||
889 | 283 | RETURNS TRIGGER LANGUAGE plpgsql VOLATILE | ||
890 | 284 | SECURITY DEFINER SET search_path TO public AS | ||
891 | 285 | $$ | ||
892 | 286 | BEGIN | ||
893 | 287 | -- This trigger only works if we are inserting, updating or deleting | ||
894 | 288 | -- a single row per statement. | ||
895 | 289 | IF TG_OP = 'INSERT' THEN | ||
896 | 290 | IF NOT (bug_row(NEW.bug)).private THEN | ||
897 | 291 | -- Public subscriptions are not aggregated. | ||
898 | 292 | RETURN NEW; | ||
899 | 293 | END IF; | ||
900 | 294 | IF TG_WHEN = 'BEFORE' THEN | ||
901 | 295 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
902 | 296 | ELSE | ||
903 | 297 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
904 | 298 | END IF; | ||
905 | 299 | PERFORM bug_summary_flush_temp_journal(); | ||
906 | 300 | RETURN NEW; | ||
907 | 301 | ELSIF TG_OP = 'DELETE' THEN | ||
908 | 302 | IF NOT (bug_row(OLD.bug)).private THEN | ||
909 | 303 | -- Public subscriptions are not aggregated. | ||
910 | 304 | RETURN OLD; | ||
911 | 305 | END IF; | ||
912 | 306 | IF TG_WHEN = 'BEFORE' THEN | ||
913 | 307 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
914 | 308 | ELSE | ||
915 | 309 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
916 | 310 | END IF; | ||
917 | 311 | PERFORM bug_summary_flush_temp_journal(); | ||
918 | 312 | RETURN OLD; | ||
919 | 313 | ELSE | ||
920 | 314 | IF (OLD.person IS DISTINCT FROM NEW.person | ||
921 | 315 | OR OLD.bug IS DISTINCT FROM NEW.bug) THEN | ||
922 | 316 | IF TG_WHEN = 'BEFORE' THEN | ||
923 | 317 | IF (bug_row(OLD.bug)).private THEN | ||
924 | 318 | -- Public subscriptions are not aggregated. | ||
925 | 319 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
926 | 320 | END IF; | ||
927 | 321 | IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN | ||
928 | 322 | -- Public subscriptions are not aggregated. | ||
929 | 323 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
930 | 324 | END IF; | ||
931 | 325 | ELSE | ||
932 | 326 | IF (bug_row(OLD.bug)).private THEN | ||
933 | 327 | -- Public subscriptions are not aggregated. | ||
934 | 328 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
935 | 329 | END IF; | ||
936 | 330 | IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN | ||
937 | 331 | -- Public subscriptions are not aggregated. | ||
938 | 332 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
939 | 333 | END IF; | ||
940 | 334 | END IF; | ||
941 | 335 | END IF; | ||
942 | 336 | PERFORM bug_summary_flush_temp_journal(); | ||
943 | 337 | RETURN NEW; | ||
944 | 338 | END IF; | ||
945 | 339 | END; | ||
946 | 340 | $$; | ||
947 | 341 | |||
948 | 342 | -- fixed to use the journal | ||
949 | 343 | CREATE OR REPLACE FUNCTION bugtag_maintain_bug_summary() RETURNS TRIGGER | ||
950 | 344 | LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS | ||
951 | 345 | $$ | ||
952 | 346 | BEGIN | ||
953 | 347 | IF TG_OP = 'INSERT' THEN | ||
954 | 348 | IF TG_WHEN = 'BEFORE' THEN | ||
955 | 349 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
956 | 350 | ELSE | ||
957 | 351 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
958 | 352 | END IF; | ||
959 | 353 | PERFORM bug_summary_flush_temp_journal(); | ||
960 | 354 | RETURN NEW; | ||
961 | 355 | ELSIF TG_OP = 'DELETE' THEN | ||
962 | 356 | IF TG_WHEN = 'BEFORE' THEN | ||
963 | 357 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
964 | 358 | ELSE | ||
965 | 359 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
966 | 360 | END IF; | ||
967 | 361 | PERFORM bug_summary_flush_temp_journal(); | ||
968 | 362 | RETURN OLD; | ||
969 | 363 | ELSE | ||
970 | 364 | IF TG_WHEN = 'BEFORE' THEN | ||
971 | 365 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
972 | 366 | IF OLD.bug <> NEW.bug THEN | ||
973 | 367 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
974 | 368 | END IF; | ||
975 | 369 | ELSE | ||
976 | 370 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
977 | 371 | IF OLD.bug <> NEW.bug THEN | ||
978 | 372 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
979 | 373 | END IF; | ||
980 | 374 | END IF; | ||
981 | 375 | PERFORM bug_summary_flush_temp_journal(); | ||
982 | 376 | RETURN NEW; | ||
983 | 377 | END IF; | ||
984 | 378 | END; | ||
985 | 379 | $$; | ||
986 | 380 | |||
987 | 381 | -- fixed to use the journal | ||
988 | 382 | CREATE OR REPLACE FUNCTION bug_maintain_bug_summary() RETURNS TRIGGER | ||
989 | 383 | LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS | ||
990 | 384 | $$ | ||
991 | 385 | BEGIN | ||
992 | 386 | -- There is no INSERT logic, as a bug will not have any summary | ||
993 | 387 | -- information until BugTask rows have been attached. | ||
994 | 388 | IF TG_OP = 'UPDATE' THEN | ||
995 | 389 | IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof | ||
996 | 390 | OR OLD.private IS DISTINCT FROM NEW.private THEN | ||
997 | 391 | PERFORM unsummarise_bug(OLD); | ||
998 | 392 | PERFORM summarise_bug(NEW); | ||
999 | 393 | END IF; | ||
1000 | 394 | |||
1001 | 395 | ELSIF TG_OP = 'DELETE' THEN | ||
1002 | 396 | PERFORM unsummarise_bug(OLD); | ||
1003 | 397 | END IF; | ||
1004 | 398 | |||
1005 | 399 | PERFORM bug_summary_flush_temp_journal(); | ||
1006 | 400 | RETURN NULL; -- Ignored - this is an AFTER trigger | ||
1007 | 401 | END; | ||
1008 | 402 | $$; | ||
1009 | 403 | |||
1010 | 404 | -- fixed to use the journal | ||
1011 | 405 | CREATE OR REPLACE FUNCTION bugtask_maintain_bug_summary() RETURNS TRIGGER | ||
1012 | 406 | LANGUAGE plpgsql VOLATILE SECURITY DEFINER SET search_path TO public AS | ||
1013 | 407 | $$ | ||
1014 | 408 | BEGIN | ||
1015 | 409 | -- This trigger only works if we are inserting, updating or deleting | ||
1016 | 410 | -- a single row per statement. | ||
1017 | 411 | |||
1018 | 412 | -- Unlike bug_maintain_bug_summary, this trigger does not have access | ||
1019 | 413 | -- to the old bug when invoked as an AFTER trigger. To work around this | ||
1020 | 414 | -- we install this trigger as both a BEFORE and an AFTER trigger. | ||
1021 | 415 | IF TG_OP = 'INSERT' THEN | ||
1022 | 416 | IF TG_WHEN = 'BEFORE' THEN | ||
1023 | 417 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
1024 | 418 | ELSE | ||
1025 | 419 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
1026 | 420 | END IF; | ||
1027 | 421 | PERFORM bug_summary_flush_temp_journal(); | ||
1028 | 422 | RETURN NEW; | ||
1029 | 423 | |||
1030 | 424 | ELSIF TG_OP = 'DELETE' THEN | ||
1031 | 425 | IF TG_WHEN = 'BEFORE' THEN | ||
1032 | 426 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
1033 | 427 | ELSE | ||
1034 | 428 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
1035 | 429 | END IF; | ||
1036 | 430 | PERFORM bug_summary_flush_temp_journal(); | ||
1037 | 431 | RETURN OLD; | ||
1038 | 432 | |||
1039 | 433 | ELSE | ||
1040 | 434 | IF (OLD.product IS DISTINCT FROM NEW.product | ||
1041 | 435 | OR OLD.productseries IS DISTINCT FROM NEW.productseries | ||
1042 | 436 | OR OLD.distribution IS DISTINCT FROM NEW.distribution | ||
1043 | 437 | OR OLD.distroseries IS DISTINCT FROM NEW.distroseries | ||
1044 | 438 | OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename | ||
1045 | 439 | OR OLD.status IS DISTINCT FROM NEW.status | ||
1046 | 440 | OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN | ||
1047 | 441 | IF TG_WHEN = 'BEFORE' THEN | ||
1048 | 442 | PERFORM unsummarise_bug(bug_row(OLD.bug)); | ||
1049 | 443 | IF OLD.bug <> NEW.bug THEN | ||
1050 | 444 | PERFORM unsummarise_bug(bug_row(NEW.bug)); | ||
1051 | 445 | END IF; | ||
1052 | 446 | ELSE | ||
1053 | 447 | PERFORM summarise_bug(bug_row(OLD.bug)); | ||
1054 | 448 | IF OLD.bug <> NEW.bug THEN | ||
1055 | 449 | PERFORM summarise_bug(bug_row(NEW.bug)); | ||
1056 | 450 | END IF; | ||
1057 | 451 | END IF; | ||
1058 | 452 | END IF; | ||
1059 | 453 | PERFORM bug_summary_flush_temp_journal(); | ||
1060 | 454 | RETURN NEW; | ||
1061 | 455 | END IF; | ||
1062 | 456 | END; | ||
1063 | 457 | $$; | ||
1064 | 458 | |||
1065 | 459 | |||
1066 | 460 | INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 1); | ||
1067 | 461 | 0 | ||
1068 | === removed file 'database/schema/patch-2208-63-2.sql' | |||
1069 | --- database/schema/patch-2208-63-2.sql 2011-06-09 11:50:58 +0000 | |||
1070 | +++ database/schema/patch-2208-63-2.sql 1970-01-01 00:00:00 +0000 | |||
1071 | @@ -1,13 +0,0 @@ | |||
1072 | 1 | -- Copyright 2011 Canonical Ltd. This software is licensed under the | ||
1073 | 2 | -- GNU Affero General Public License version 3 (see the file LICENSE). | ||
1074 | 3 | |||
1075 | 4 | SET client_min_messages=ERROR; | ||
1076 | 5 | |||
1077 | 6 | CREATE INDEX bugsummary__milestone__idx | ||
1078 | 7 | ON BugSummary(milestone) WHERE milestone IS NOT NULL; | ||
1079 | 8 | |||
1080 | 9 | |||
1081 | 10 | CREATE INDEX bugsummary__full__idx | ||
1082 | 11 | ON BugSummary(status, product, productseries, distribution, distroseries, sourcepackagename, viewed_by, milestone, tag); | ||
1083 | 12 | |||
1084 | 13 | INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 2); | ||
1085 | 14 | 0 | ||
1086 | === removed file 'database/schema/patch-2208-63-3.sql' | |||
1087 | --- database/schema/patch-2208-63-3.sql 2011-06-09 22:27:34 +0000 | |||
1088 | +++ database/schema/patch-2208-63-3.sql 1970-01-01 00:00:00 +0000 | |||
1089 | @@ -1,55 +0,0 @@ | |||
1090 | 1 | -- Copyright 2011 Canonical Ltd. This software is licensed under the | ||
1091 | 2 | -- GNU Affero General Public License version 3 (see the file LICENSE). | ||
1092 | 3 | |||
1093 | 4 | SET client_min_messages=ERROR; | ||
1094 | 5 | |||
1095 | 6 | |||
1096 | 7 | -- Create a journal for BugSummary updates. | ||
1097 | 8 | -- This is a separate DB patch as the table needs to be created and | ||
1098 | 9 | -- added to replication before triggers are created, and we want to | ||
1099 | 10 | -- do this live. We discussed not replicating this table, but this | ||
1100 | 11 | -- would break our ability to failover to a new master. | ||
1101 | 12 | |||
1102 | 13 | CREATE TABLE BugSummaryJournal ( | ||
1103 | 14 | id serial PRIMARY KEY, | ||
1104 | 15 | count INTEGER NOT NULL default 0, | ||
1105 | 16 | product INTEGER REFERENCES Product ON DELETE CASCADE, | ||
1106 | 17 | productseries INTEGER REFERENCES ProductSeries ON DELETE CASCADE, | ||
1107 | 18 | distribution INTEGER REFERENCES Distribution ON DELETE CASCADE, | ||
1108 | 19 | distroseries INTEGER REFERENCES DistroSeries ON DELETE CASCADE, | ||
1109 | 20 | sourcepackagename INTEGER REFERENCES SourcePackageName ON DELETE CASCADE, | ||
1110 | 21 | viewed_by INTEGER, | ||
1111 | 22 | tag TEXT, | ||
1112 | 23 | status INTEGER NOT NULL, | ||
1113 | 24 | milestone INTEGER REFERENCES Milestone ON DELETE CASCADE); | ||
1114 | 25 | |||
1115 | 26 | -- Fat index for fast lookups | ||
1116 | 27 | CREATE INDEX bugsummaryjournal__full__idx ON BugSummaryJournal ( | ||
1117 | 28 | status, product, productseries, distribution, distroseries, | ||
1118 | 29 | sourcepackagename, viewed_by, milestone, tag); | ||
1119 | 30 | |||
1120 | 31 | -- Indexes for fast deletions. | ||
1121 | 32 | CREATE INDEX bugsummaryjournal__viewed_by__idx | ||
1122 | 33 | ON BugSummaryJournal(viewed_by) WHERE viewed_by IS NOT NULL; | ||
1123 | 34 | CREATE INDEX bugsummaryjournal__milestone__idx | ||
1124 | 35 | ON BugSummaryJournal(milestone) WHERE milestone IS NOT NULL; | ||
1125 | 36 | |||
1126 | 37 | |||
1127 | 38 | -- Combined view so we don't have to manually collate rows from both tables. | ||
1128 | 39 | -- Note that we flip the sign of the id column of BugSummaryJournal to avoid | ||
1129 | 40 | -- clashes. This is enough to keep Storm happy as it never needs to update | ||
1130 | 41 | -- this table, and there are no other suitable primary keys. | ||
1131 | 42 | -- We don't SUM() rows here to ensure PostgreSQL has the most hope of | ||
1132 | 43 | -- generating good query plans when we query this view. | ||
1133 | 44 | CREATE OR REPLACE VIEW CombinedBugSummary AS ( | ||
1134 | 45 | SELECT | ||
1135 | 46 | id, count, product, productseries, distribution, distroseries, | ||
1136 | 47 | sourcepackagename, viewed_by, tag, status, milestone | ||
1137 | 48 | FROM BugSummary | ||
1138 | 49 | UNION ALL | ||
1139 | 50 | SELECT | ||
1140 | 51 | -id as id, count, product, productseries, distribution, distroseries, | ||
1141 | 52 | sourcepackagename, viewed_by, tag, status, milestone | ||
1142 | 53 | FROM BugSummaryJournal); | ||
1143 | 54 | |||
1144 | 55 | INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 3); | ||
1145 | 56 | 0 | ||
1146 | === removed file 'database/schema/patch-2208-63-4.sql' | |||
1147 | --- database/schema/patch-2208-63-4.sql 2011-06-09 20:46:15 +0000 | |||
1148 | +++ database/schema/patch-2208-63-4.sql 1970-01-01 00:00:00 +0000 | |||
1149 | @@ -1,171 +0,0 @@ | |||
1150 | 1 | -- Copyright 2011 Canonical Ltd. This software is licensed under the | ||
1151 | 2 | -- GNU Affero General Public License version 3 (see the file LICENSE). | ||
1152 | 3 | |||
1153 | 4 | SET client_min_messages=ERROR; | ||
1154 | 5 | |||
1155 | 6 | CREATE OR REPLACE FUNCTION bugsummary_journal_ins(d bugsummary) | ||
1156 | 7 | RETURNS VOID | ||
1157 | 8 | LANGUAGE plpgsql AS | ||
1158 | 9 | $$ | ||
1159 | 10 | BEGIN | ||
1160 | 11 | IF d.count <> 0 THEN | ||
1161 | 12 | INSERT INTO BugSummaryJournal ( | ||
1162 | 13 | count, product, productseries, distribution, | ||
1163 | 14 | distroseries, sourcepackagename, viewed_by, tag, | ||
1164 | 15 | status, milestone) | ||
1165 | 16 | VALUES ( | ||
1166 | 17 | d.count, d.product, d.productseries, d.distribution, | ||
1167 | 18 | d.distroseries, d.sourcepackagename, d.viewed_by, d.tag, | ||
1168 | 19 | d.status, d.milestone); | ||
1169 | 20 | END IF; | ||
1170 | 21 | END; | ||
1171 | 22 | $$; | ||
1172 | 23 | |||
1173 | 24 | COMMENT ON FUNCTION bugsummary_journal_ins(bugsummary) IS | ||
1174 | 25 | 'Add an entry into BugSummaryJournal'; | ||
1175 | 26 | |||
1176 | 27 | |||
1177 | 28 | CREATE OR REPLACE FUNCTION bugsummary_rollup_journal() RETURNS VOID | ||
1178 | 29 | LANGUAGE plpgsql VOLATILE | ||
1179 | 30 | SECURITY DEFINER SET search_path TO public AS | ||
1180 | 31 | $$ | ||
1181 | 32 | DECLARE | ||
1182 | 33 | d bugsummary%ROWTYPE; | ||
1183 | 34 | max_id integer; | ||
1184 | 35 | BEGIN | ||
1185 | 36 | -- Lock so we don't content with other invokations of this | ||
1186 | 37 | -- function. We can happily lock the BugSummary table for writes | ||
1187 | 38 | -- as this function is the only thing that updates that table. | ||
1188 | 39 | -- BugSummaryJournal remains unlocked so nothing should be blocked. | ||
1189 | 40 | LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE; | ||
1190 | 41 | |||
1191 | 42 | SELECT MAX(id) INTO max_id FROM BugSummaryJournal; | ||
1192 | 43 | |||
1193 | 44 | FOR d IN | ||
1194 | 45 | SELECT | ||
1195 | 46 | NULL as id, | ||
1196 | 47 | SUM(count), | ||
1197 | 48 | product, | ||
1198 | 49 | productseries, | ||
1199 | 50 | distribution, | ||
1200 | 51 | distroseries, | ||
1201 | 52 | sourcepackagename, | ||
1202 | 53 | viewed_by, | ||
1203 | 54 | tag, | ||
1204 | 55 | status, | ||
1205 | 56 | milestone | ||
1206 | 57 | FROM BugSummaryJournal | ||
1207 | 58 | WHERE id <= max_id | ||
1208 | 59 | GROUP BY | ||
1209 | 60 | product, productseries, distribution, distroseries, | ||
1210 | 61 | sourcepackagename, viewed_by, tag, status, milestone | ||
1211 | 62 | HAVING sum(count) <> 0 | ||
1212 | 63 | LOOP | ||
1213 | 64 | IF d.count < 0 THEN | ||
1214 | 65 | PERFORM bug_summary_dec(d); | ||
1215 | 66 | ELSIF d.count > 0 THEN | ||
1216 | 67 | PERFORM bug_summary_inc(d); | ||
1217 | 68 | END IF; | ||
1218 | 69 | END LOOP; | ||
1219 | 70 | |||
1220 | 71 | DELETE FROM BugSummaryJournal WHERE id <= max_id; | ||
1221 | 72 | END; | ||
1222 | 73 | $$; | ||
1223 | 74 | |||
1224 | 75 | CREATE OR REPLACE FUNCTION bug_summary_dec(bugsummary) RETURNS VOID | ||
1225 | 76 | LANGUAGE SQL AS | ||
1226 | 77 | $$ | ||
1227 | 78 | -- We own the row reference, so in the absence of bugs this cannot | ||
1228 | 79 | -- fail - just decrement the row. | ||
1229 | 80 | UPDATE BugSummary SET count = count + $1.count | ||
1230 | 81 | WHERE | ||
1231 | 82 | product IS NOT DISTINCT FROM $1.product | ||
1232 | 83 | AND productseries IS NOT DISTINCT FROM $1.productseries | ||
1233 | 84 | AND distribution IS NOT DISTINCT FROM $1.distribution | ||
1234 | 85 | AND distroseries IS NOT DISTINCT FROM $1.distroseries | ||
1235 | 86 | AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename | ||
1236 | 87 | AND viewed_by IS NOT DISTINCT FROM $1.viewed_by | ||
1237 | 88 | AND tag IS NOT DISTINCT FROM $1.tag | ||
1238 | 89 | AND status IS NOT DISTINCT FROM $1.status | ||
1239 | 90 | AND milestone IS NOT DISTINCT FROM $1.milestone; | ||
1240 | 91 | -- gc the row (perhaps should be garbo but easy enough to add here: | ||
1241 | 92 | DELETE FROM bugsummary | ||
1242 | 93 | WHERE | ||
1243 | 94 | count=0 | ||
1244 | 95 | AND product IS NOT DISTINCT FROM $1.product | ||
1245 | 96 | AND productseries IS NOT DISTINCT FROM $1.productseries | ||
1246 | 97 | AND distribution IS NOT DISTINCT FROM $1.distribution | ||
1247 | 98 | AND distroseries IS NOT DISTINCT FROM $1.distroseries | ||
1248 | 99 | AND sourcepackagename IS NOT DISTINCT FROM $1.sourcepackagename | ||
1249 | 100 | AND viewed_by IS NOT DISTINCT FROM $1.viewed_by | ||
1250 | 101 | AND tag IS NOT DISTINCT FROM $1.tag | ||
1251 | 102 | AND status IS NOT DISTINCT FROM $1.status | ||
1252 | 103 | AND milestone IS NOT DISTINCT FROM $1.milestone; | ||
1253 | 104 | -- If its not found then someone else also dec'd and won concurrently. | ||
1254 | 105 | $$; | ||
1255 | 106 | |||
1256 | 107 | CREATE OR REPLACE FUNCTION bug_summary_inc(d bugsummary) RETURNS VOID | ||
1257 | 108 | LANGUAGE plpgsql AS | ||
1258 | 109 | $$ | ||
1259 | 110 | BEGIN | ||
1260 | 111 | -- Shameless adaption from postgresql manual | ||
1261 | 112 | LOOP | ||
1262 | 113 | -- first try to update the row | ||
1263 | 114 | UPDATE BugSummary SET count = count + d.count | ||
1264 | 115 | WHERE | ||
1265 | 116 | product IS NOT DISTINCT FROM d.product | ||
1266 | 117 | AND productseries IS NOT DISTINCT FROM d.productseries | ||
1267 | 118 | AND distribution IS NOT DISTINCT FROM d.distribution | ||
1268 | 119 | AND distroseries IS NOT DISTINCT FROM d.distroseries | ||
1269 | 120 | AND sourcepackagename IS NOT DISTINCT FROM d.sourcepackagename | ||
1270 | 121 | AND viewed_by IS NOT DISTINCT FROM d.viewed_by | ||
1271 | 122 | AND tag IS NOT DISTINCT FROM d.tag | ||
1272 | 123 | AND status IS NOT DISTINCT FROM d.status | ||
1273 | 124 | AND milestone IS NOT DISTINCT FROM d.milestone; | ||
1274 | 125 | IF found THEN | ||
1275 | 126 | RETURN; | ||
1276 | 127 | END IF; | ||
1277 | 128 | -- not there, so try to insert the key | ||
1278 | 129 | -- if someone else inserts the same key concurrently, | ||
1279 | 130 | -- we could get a unique-key failure | ||
1280 | 131 | BEGIN | ||
1281 | 132 | INSERT INTO BugSummary( | ||
1282 | 133 | count, product, productseries, distribution, | ||
1283 | 134 | distroseries, sourcepackagename, viewed_by, tag, | ||
1284 | 135 | status, milestone) | ||
1285 | 136 | VALUES ( | ||
1286 | 137 | d.count, d.product, d.productseries, d.distribution, | ||
1287 | 138 | d.distroseries, d.sourcepackagename, d.viewed_by, d.tag, | ||
1288 | 139 | d.status, d.milestone); | ||
1289 | 140 | RETURN; | ||
1290 | 141 | EXCEPTION WHEN unique_violation THEN | ||
1291 | 142 | -- do nothing, and loop to try the UPDATE again | ||
1292 | 143 | END; | ||
1293 | 144 | END LOOP; | ||
1294 | 145 | END; | ||
1295 | 146 | $$; | ||
1296 | 147 | |||
1297 | 148 | COMMENT ON FUNCTION bugsummary_rollup_journal() IS | ||
1298 | 149 | 'Collate and migrate rows from BugSummaryJournal to BugSummary'; | ||
1299 | 150 | |||
1300 | 151 | CREATE OR REPLACE FUNCTION bug_summary_flush_temp_journal() RETURNS VOID | ||
1301 | 152 | LANGUAGE plpgsql VOLATILE AS | ||
1302 | 153 | $$ | ||
1303 | 154 | DECLARE | ||
1304 | 155 | d bugsummary%ROWTYPE; | ||
1305 | 156 | BEGIN | ||
1306 | 157 | -- may get called even though no summaries were made (for simplicity in the | ||
1307 | 158 | -- callers) | ||
1308 | 159 | PERFORM ensure_bugsummary_temp_journal(); | ||
1309 | 160 | FOR d IN SELECT * FROM bugsummary_temp_journal LOOP | ||
1310 | 161 | PERFORM bugsummary_journal_ins(d); | ||
1311 | 162 | END LOOP; | ||
1312 | 163 | TRUNCATE bugsummary_temp_journal; | ||
1313 | 164 | END; | ||
1314 | 165 | $$; | ||
1315 | 166 | |||
1316 | 167 | COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS | ||
1317 | 168 | 'flush the temporary bugsummary journal into the bugsummaryjournal table'; | ||
1318 | 169 | |||
1319 | 170 | |||
1320 | 171 | INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 63, 4); | ||
1321 | 172 | 0 | ||
1322 | === modified file 'lib/canonical/launchpad/webapp/errorlog.py' | |||
1323 | --- lib/canonical/launchpad/webapp/errorlog.py 2011-06-01 19:23:24 +0000 | |||
1324 | +++ lib/canonical/launchpad/webapp/errorlog.py 2011-06-14 11:48:38 +0000 | |||
1325 | @@ -40,6 +40,7 @@ | |||
1326 | 40 | IErrorReport, | 40 | IErrorReport, |
1327 | 41 | IErrorReportEvent, | 41 | IErrorReportEvent, |
1328 | 42 | IErrorReportRequest, | 42 | IErrorReportRequest, |
1329 | 43 | IUnloggedException, | ||
1330 | 43 | ) | 44 | ) |
1331 | 44 | from canonical.launchpad.webapp.opstats import OpStats | 45 | from canonical.launchpad.webapp.opstats import OpStats |
1332 | 45 | from canonical.launchpad.webapp.vhosts import allvhosts | 46 | from canonical.launchpad.webapp.vhosts import allvhosts |
1333 | @@ -372,7 +373,18 @@ | |||
1334 | 372 | notify(ErrorReportEvent(entry)) | 373 | notify(ErrorReportEvent(entry)) |
1335 | 373 | return entry | 374 | return entry |
1336 | 374 | 375 | ||
1338 | 375 | def _isIgnoredException(self, strtype, request=None): | 376 | def _isIgnoredException(self, strtype, request=None, exception=None): |
1339 | 377 | """Should the given exception generate an OOPS or be ignored? | ||
1340 | 378 | |||
1341 | 379 | Exceptions will be ignored if they | ||
1342 | 380 | - are specially tagged as being ignorable by having the marker | ||
1343 | 381 | interface IUnloggedException | ||
1344 | 382 | - are of a type included in self._ignored_exceptions, or | ||
1345 | 383 | - were requested with an off-site REFERRER header and are of a | ||
1346 | 384 | type included in self._ignored_exceptions_for_offsite_referer | ||
1347 | 385 | """ | ||
1348 | 386 | if IUnloggedException.providedBy(exception): | ||
1349 | 387 | return True | ||
1350 | 376 | if strtype in self._ignored_exceptions: | 388 | if strtype in self._ignored_exceptions: |
1351 | 377 | return True | 389 | return True |
1352 | 378 | if strtype in self._ignored_exceptions_for_offsite_referer: | 390 | if strtype in self._ignored_exceptions_for_offsite_referer: |
1353 | @@ -409,7 +421,7 @@ | |||
1354 | 409 | tb_text = None | 421 | tb_text = None |
1355 | 410 | 422 | ||
1356 | 411 | strtype = str(getattr(info[0], '__name__', info[0])) | 423 | strtype = str(getattr(info[0], '__name__', info[0])) |
1358 | 412 | if self._isIgnoredException(strtype, request): | 424 | if self._isIgnoredException(strtype, request, info[1]): |
1359 | 413 | return | 425 | return |
1360 | 414 | 426 | ||
1361 | 415 | if not isinstance(info[2], basestring): | 427 | if not isinstance(info[2], basestring): |
1362 | 416 | 428 | ||
1363 | === modified file 'lib/canonical/launchpad/webapp/interfaces.py' | |||
1364 | --- lib/canonical/launchpad/webapp/interfaces.py 2011-04-27 16:01:06 +0000 | |||
1365 | +++ lib/canonical/launchpad/webapp/interfaces.py 2011-06-14 11:48:38 +0000 | |||
1366 | @@ -178,7 +178,8 @@ | |||
1367 | 178 | class ILink(ILinkData): | 178 | class ILink(ILinkData): |
1368 | 179 | """An object that represents a link in a menu. | 179 | """An object that represents a link in a menu. |
1369 | 180 | 180 | ||
1371 | 181 | The attributes name, url and linked may be set by the menus infrastructure. | 181 | The attributes name, url and linked may be set by the menus |
1372 | 182 | infrastructure. | ||
1373 | 182 | """ | 183 | """ |
1374 | 183 | 184 | ||
1375 | 184 | name = Attribute("The name of this link in Python data structures.") | 185 | name = Attribute("The name of this link in Python data structures.") |
1376 | @@ -262,6 +263,7 @@ | |||
1377 | 262 | (object_url_requested_for, broken_link_in_chain) | 263 | (object_url_requested_for, broken_link_in_chain) |
1378 | 263 | ) | 264 | ) |
1379 | 264 | 265 | ||
1380 | 266 | |||
1381 | 265 | # XXX kiko 2007-02-08: this needs reconsideration if we are to make it a truly | 267 | # XXX kiko 2007-02-08: this needs reconsideration if we are to make it a truly |
1382 | 266 | # generic thing. The problem lies in the fact that half of this (user, login, | 268 | # generic thing. The problem lies in the fact that half of this (user, login, |
1383 | 267 | # time zone, developer) is actually useful inside webapp/, and the other half | 269 | # time zone, developer) is actually useful inside webapp/, and the other half |
1384 | @@ -307,6 +309,7 @@ | |||
1385 | 307 | connection blows up. | 309 | connection blows up. |
1386 | 308 | ''' | 310 | ''' |
1387 | 309 | 311 | ||
1388 | 312 | |||
1389 | 310 | # | 313 | # |
1390 | 311 | # Request | 314 | # Request |
1391 | 312 | # | 315 | # |
1392 | @@ -406,6 +409,7 @@ | |||
1393 | 406 | 409 | ||
1394 | 407 | class CookieAuthLoggedInEvent: | 410 | class CookieAuthLoggedInEvent: |
1395 | 408 | implements(ILoggedInEvent) | 411 | implements(ILoggedInEvent) |
1396 | 412 | |||
1397 | 409 | def __init__(self, request, login): | 413 | def __init__(self, request, login): |
1398 | 410 | self.request = request | 414 | self.request = request |
1399 | 411 | self.login = login | 415 | self.login = login |
1400 | @@ -413,6 +417,7 @@ | |||
1401 | 413 | 417 | ||
1402 | 414 | class CookieAuthPrincipalIdentifiedEvent: | 418 | class CookieAuthPrincipalIdentifiedEvent: |
1403 | 415 | implements(IPrincipalIdentifiedEvent) | 419 | implements(IPrincipalIdentifiedEvent) |
1404 | 420 | |||
1405 | 416 | def __init__(self, principal, request, login): | 421 | def __init__(self, principal, request, login): |
1406 | 417 | self.principal = principal | 422 | self.principal = principal |
1407 | 418 | self.request = request | 423 | self.request = request |
1408 | @@ -421,6 +426,7 @@ | |||
1409 | 421 | 426 | ||
1410 | 422 | class BasicAuthLoggedInEvent: | 427 | class BasicAuthLoggedInEvent: |
1411 | 423 | implements(ILoggedInEvent, IPrincipalIdentifiedEvent) | 428 | implements(ILoggedInEvent, IPrincipalIdentifiedEvent) |
1412 | 429 | |||
1413 | 424 | def __init__(self, request, login, principal): | 430 | def __init__(self, request, login, principal): |
1414 | 425 | # these one from ILoggedInEvent | 431 | # these one from ILoggedInEvent |
1415 | 426 | self.login = login | 432 | self.login = login |
1416 | @@ -436,6 +442,7 @@ | |||
1417 | 436 | 442 | ||
1418 | 437 | class LoggedOutEvent: | 443 | class LoggedOutEvent: |
1419 | 438 | implements(ILoggedOutEvent) | 444 | implements(ILoggedOutEvent) |
1420 | 445 | |||
1421 | 439 | def __init__(self, request): | 446 | def __init__(self, request): |
1422 | 440 | self.request = request | 447 | self.request = request |
1423 | 441 | 448 | ||
1424 | @@ -527,6 +534,7 @@ | |||
1425 | 527 | you're using right now. | 534 | you're using right now. |
1426 | 528 | """) | 535 | """) |
1427 | 529 | 536 | ||
1428 | 537 | |||
1429 | 530 | class AccessLevel(DBEnumeratedType): | 538 | class AccessLevel(DBEnumeratedType): |
1430 | 531 | """The level of access any given principal has.""" | 539 | """The level of access any given principal has.""" |
1431 | 532 | use_template(OAuthPermission, exclude='UNAUTHORIZED') | 540 | use_template(OAuthPermission, exclude='UNAUTHORIZED') |
1432 | @@ -553,10 +561,10 @@ | |||
1433 | 553 | 561 | ||
1434 | 554 | class BrowserNotificationLevel: | 562 | class BrowserNotificationLevel: |
1435 | 555 | """Matches the standard logging levels.""" | 563 | """Matches the standard logging levels.""" |
1440 | 556 | DEBUG = logging.DEBUG # A debugging message | 564 | DEBUG = logging.DEBUG # debugging message |
1441 | 557 | INFO = logging.INFO # simple confirmation of a change | 565 | INFO = logging.INFO # simple confirmation of a change |
1442 | 558 | WARNING = logging.WARNING # action will not be successful unless you ... | 566 | WARNING = logging.WARNING # action will not be successful unless you ... |
1443 | 559 | ERROR = logging.ERROR # the previous action did not succeed, and why | 567 | ERROR = logging.ERROR # the previous action did not succeed, and why |
1444 | 560 | 568 | ||
1445 | 561 | ALL_LEVELS = (DEBUG, INFO, WARNING, ERROR) | 569 | ALL_LEVELS = (DEBUG, INFO, WARNING, ERROR) |
1446 | 562 | 570 | ||
1447 | @@ -646,6 +654,10 @@ | |||
1448 | 646 | """ | 654 | """ |
1449 | 647 | 655 | ||
1450 | 648 | 656 | ||
1451 | 657 | class IUnloggedException(Interface): | ||
1452 | 658 | """An exception that should not be logged in an OOPS report (marker).""" | ||
1453 | 659 | |||
1454 | 660 | |||
1455 | 649 | class IErrorReportEvent(IObjectEvent): | 661 | class IErrorReportEvent(IObjectEvent): |
1456 | 650 | """A new error report has been created.""" | 662 | """A new error report has been created.""" |
1457 | 651 | 663 | ||
1458 | @@ -673,6 +685,7 @@ | |||
1459 | 673 | description=u"""an identifier for the exception, or None if no | 685 | description=u"""an identifier for the exception, or None if no |
1460 | 674 | exception has occurred""") | 686 | exception has occurred""") |
1461 | 675 | 687 | ||
1462 | 688 | |||
1463 | 676 | # | 689 | # |
1464 | 677 | # Batch Navigation | 690 | # Batch Navigation |
1465 | 678 | # | 691 | # |
1466 | @@ -720,12 +733,12 @@ | |||
1467 | 720 | # Database policies | 733 | # Database policies |
1468 | 721 | # | 734 | # |
1469 | 722 | 735 | ||
1471 | 723 | MAIN_STORE = 'main' # The main database. | 736 | MAIN_STORE = 'main' # The main database. |
1472 | 724 | ALL_STORES = frozenset([MAIN_STORE]) | 737 | ALL_STORES = frozenset([MAIN_STORE]) |
1473 | 725 | 738 | ||
1477 | 726 | DEFAULT_FLAVOR = 'default' # Default flavor for current state. | 739 | DEFAULT_FLAVOR = 'default' # Default flavor for current state. |
1478 | 727 | MASTER_FLAVOR = 'master' # The master database. | 740 | MASTER_FLAVOR = 'master' # The master database. |
1479 | 728 | SLAVE_FLAVOR = 'slave' # A slave database. | 741 | SLAVE_FLAVOR = 'slave' # A slave database. |
1480 | 729 | 742 | ||
1481 | 730 | 743 | ||
1482 | 731 | class IDatabasePolicy(Interface): | 744 | class IDatabasePolicy(Interface): |
1483 | @@ -856,7 +869,6 @@ | |||
1484 | 856 | 869 | ||
1485 | 857 | request = Attribute("The request the event is about") | 870 | request = Attribute("The request the event is about") |
1486 | 858 | 871 | ||
1487 | 859 | |||
1488 | 860 | class StartRequestEvent: | 872 | class StartRequestEvent: |
1489 | 861 | """An event fired once at the start of requests. | 873 | """An event fired once at the start of requests. |
1490 | 862 | 874 | ||
1491 | 863 | 875 | ||
1492 | === modified file 'lib/canonical/launchpad/webapp/tests/test_errorlog.py' | |||
1493 | --- lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-06-01 19:35:22 +0000 | |||
1494 | +++ lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-06-14 11:48:38 +0000 | |||
1495 | @@ -41,7 +41,10 @@ | |||
1496 | 41 | OopsLoggingHandler, | 41 | OopsLoggingHandler, |
1497 | 42 | ScriptRequest, | 42 | ScriptRequest, |
1498 | 43 | ) | 43 | ) |
1500 | 44 | from canonical.launchpad.webapp.interfaces import NoReferrerError | 44 | from canonical.launchpad.webapp.interfaces import ( |
1501 | 45 | IUnloggedException, | ||
1502 | 46 | NoReferrerError, | ||
1503 | 47 | ) | ||
1504 | 45 | from canonical.testing import reset_logging | 48 | from canonical.testing import reset_logging |
1505 | 46 | from lp.app.errors import ( | 49 | from lp.app.errors import ( |
1506 | 47 | GoneError, | 50 | GoneError, |
1507 | @@ -50,6 +53,7 @@ | |||
1508 | 50 | from lp.services.log.uniquefileallocator import UniqueFileAllocator | 53 | from lp.services.log.uniquefileallocator import UniqueFileAllocator |
1509 | 51 | from lp.services.osutils import remove_tree | 54 | from lp.services.osutils import remove_tree |
1510 | 52 | from lp.testing import TestCase | 55 | from lp.testing import TestCase |
1511 | 56 | from lp_sitecustomize import customize_get_converter | ||
1512 | 53 | 57 | ||
1513 | 54 | 58 | ||
1514 | 55 | UTC = pytz.timezone('UTC') | 59 | UTC = pytz.timezone('UTC') |
1515 | @@ -967,7 +971,7 @@ | |||
1516 | 967 | self.assertIs(None, self.error_utility.getLastOopsReport()) | 971 | self.assertIs(None, self.error_utility.getLastOopsReport()) |
1517 | 968 | 972 | ||
1518 | 969 | 973 | ||
1520 | 970 | class Test404Oops(testtools.TestCase): | 974 | class TestOopsIgnoring(testtools.TestCase): |
1521 | 971 | 975 | ||
1522 | 972 | def test_offsite_404_ignored(self): | 976 | def test_offsite_404_ignored(self): |
1523 | 973 | # A request originating from another site that generates a NotFound | 977 | # A request originating from another site that generates a NotFound |
1524 | @@ -990,6 +994,78 @@ | |||
1525 | 990 | request = dict() | 994 | request = dict() |
1526 | 991 | self.assertTrue(utility._isIgnoredException('NotFound', request)) | 995 | self.assertTrue(utility._isIgnoredException('NotFound', request)) |
1527 | 992 | 996 | ||
1528 | 997 | def test_marked_exception_is_ignored(self): | ||
1529 | 998 | # If an exception has been marked as ignorable, then it is ignored. | ||
1530 | 999 | utility = ErrorReportingUtility() | ||
1531 | 1000 | exception = Exception() | ||
1532 | 1001 | directlyProvides(exception, IUnloggedException) | ||
1533 | 1002 | self.assertTrue( | ||
1534 | 1003 | utility._isIgnoredException('RuntimeError', exception=exception)) | ||
1535 | 1004 | |||
1536 | 1005 | def test_unmarked_exception_generates_oops(self): | ||
1537 | 1006 | # If an exception has not been marked as ignorable, then it is not. | ||
1538 | 1007 | utility = ErrorReportingUtility() | ||
1539 | 1008 | exception = Exception() | ||
1540 | 1009 | self.assertFalse( | ||
1541 | 1010 | utility._isIgnoredException('RuntimeError', exception=exception)) | ||
1542 | 1011 | |||
1543 | 1012 | |||
1544 | 1013 | class TestWrappedParameterConverter(testtools.TestCase): | ||
1545 | 1014 | """Make sure URL parameter type conversions don't generate OOPS reports""" | ||
1546 | 1015 | |||
1547 | 1016 | def test_return_value_untouched(self): | ||
1548 | 1017 | # When a converter succeeds, its return value is passed through the | ||
1549 | 1018 | # wrapper untouched. | ||
1550 | 1019 | |||
1551 | 1020 | class FauxZopePublisherBrowserModule: | ||
1552 | 1021 | def get_converter(self, type_): | ||
1553 | 1022 | def the_converter(value): | ||
1554 | 1023 | return 'converted %r to %s' % (value, type_) | ||
1555 | 1024 | return the_converter | ||
1556 | 1025 | |||
1557 | 1026 | module = FauxZopePublisherBrowserModule() | ||
1558 | 1027 | customize_get_converter(module) | ||
1559 | 1028 | converter = module.get_converter('int') | ||
1560 | 1029 | self.assertEqual("converted '42' to int", converter('42')) | ||
1561 | 1030 | |||
1562 | 1031 | def test_value_errors_marked(self): | ||
1563 | 1032 | # When a ValueError is raised by the wrapped converter, the exception | ||
1564 | 1033 | # is marked with IUnloggedException so the OOPS machinery knows that a | ||
1565 | 1034 | # report should not be logged. | ||
1566 | 1035 | |||
1567 | 1036 | class FauxZopePublisherBrowserModule: | ||
1568 | 1037 | def get_converter(self, type_): | ||
1569 | 1038 | def the_converter(value): | ||
1570 | 1039 | raise ValueError | ||
1571 | 1040 | return the_converter | ||
1572 | 1041 | |||
1573 | 1042 | module = FauxZopePublisherBrowserModule() | ||
1574 | 1043 | customize_get_converter(module) | ||
1575 | 1044 | converter = module.get_converter('int') | ||
1576 | 1045 | try: | ||
1577 | 1046 | converter(42) | ||
1578 | 1047 | except ValueError, e: | ||
1579 | 1048 | self.assertTrue(IUnloggedException.providedBy(e)) | ||
1580 | 1049 | |||
1581 | 1050 | def test_other_errors_not_marked(self): | ||
1582 | 1051 | # When an exception other than ValueError is raised by the wrapped | ||
1583 | 1052 | # converter, the exception is not marked with IUnloggedException an | ||
1584 | 1053 | # OOPS report will be created. | ||
1585 | 1054 | |||
1586 | 1055 | class FauxZopePublisherBrowserModule: | ||
1587 | 1056 | def get_converter(self, type_): | ||
1588 | 1057 | def the_converter(value): | ||
1589 | 1058 | raise RuntimeError | ||
1590 | 1059 | return the_converter | ||
1591 | 1060 | |||
1592 | 1061 | module = FauxZopePublisherBrowserModule() | ||
1593 | 1062 | customize_get_converter(module) | ||
1594 | 1063 | converter = module.get_converter('int') | ||
1595 | 1064 | try: | ||
1596 | 1065 | converter(42) | ||
1597 | 1066 | except RuntimeError, e: | ||
1598 | 1067 | self.assertFalse(IUnloggedException.providedBy(e)) | ||
1599 | 1068 | |||
1600 | 993 | 1069 | ||
1601 | 994 | def test_suite(): | 1070 | def test_suite(): |
1602 | 995 | return unittest.TestLoader().loadTestsFromName(__name__) | 1071 | return unittest.TestLoader().loadTestsFromName(__name__) |
1603 | 996 | 1072 | ||
1604 | === modified file 'lib/lp_sitecustomize.py' | |||
1605 | --- lib/lp_sitecustomize.py 2011-04-05 12:41:25 +0000 | |||
1606 | +++ lib/lp_sitecustomize.py 2011-06-14 11:48:38 +0000 | |||
1607 | @@ -16,12 +16,15 @@ | |||
1608 | 16 | ) | 16 | ) |
1609 | 17 | 17 | ||
1610 | 18 | from bzrlib.branch import Branch | 18 | from bzrlib.branch import Branch |
1611 | 19 | from canonical.launchpad.webapp.interfaces import IUnloggedException | ||
1612 | 19 | from lp.services.log import loglevels | 20 | from lp.services.log import loglevels |
1613 | 20 | from lp.services.log.logger import LaunchpadLogger | 21 | from lp.services.log.logger import LaunchpadLogger |
1614 | 21 | from lp.services.log.mappingfilter import MappingFilter | 22 | from lp.services.log.mappingfilter import MappingFilter |
1615 | 22 | from lp.services.log.nullhandler import NullHandler | 23 | from lp.services.log.nullhandler import NullHandler |
1616 | 23 | from lp.services.mime import customizeMimetypes | 24 | from lp.services.mime import customizeMimetypes |
1617 | 25 | from zope.interface import alsoProvides | ||
1618 | 24 | from zope.security import checker | 26 | from zope.security import checker |
1619 | 27 | import zope.publisher.browser | ||
1620 | 25 | 28 | ||
1621 | 26 | 29 | ||
1622 | 27 | def add_custom_loglevels(): | 30 | def add_custom_loglevels(): |
1623 | @@ -136,6 +139,33 @@ | |||
1624 | 136 | silence_transaction_logger() | 139 | silence_transaction_logger() |
1625 | 137 | 140 | ||
1626 | 138 | 141 | ||
1627 | 142 | def customize_get_converter(zope_publisher_browser=zope.publisher.browser): | ||
1628 | 143 | """URL parameter conversion errors shouldn't generate an OOPS report. | ||
1629 | 144 | |||
1630 | 145 | This injects (monkey patches) our wrapper around get_converter so improper | ||
1631 | 146 | use of parameter type converters (like http://...?foo=bar:int) won't | ||
1632 | 147 | generate OOPS reports. | ||
1633 | 148 | """ | ||
1634 | 149 | |||
1635 | 150 | original_get_converter = zope_publisher_browser.get_converter | ||
1636 | 151 | |||
1637 | 152 | def get_converter(*args, **kws): | ||
1638 | 153 | """Get a type converter but turn off OOPS reporting if it fails.""" | ||
1639 | 154 | converter = original_get_converter(*args, **kws) | ||
1640 | 155 | |||
1641 | 156 | def wrapped_converter(v): | ||
1642 | 157 | try: | ||
1643 | 158 | return converter(v) | ||
1644 | 159 | except ValueError, e: | ||
1645 | 160 | # Mark the exception as not being OOPS-worthy. | ||
1646 | 161 | alsoProvides(e, IUnloggedException) | ||
1647 | 162 | raise | ||
1648 | 163 | |||
1649 | 164 | return wrapped_converter | ||
1650 | 165 | |||
1651 | 166 | zope_publisher_browser.get_converter = get_converter | ||
1652 | 167 | |||
1653 | 168 | |||
1654 | 139 | def main(instance_name): | 169 | def main(instance_name): |
1655 | 140 | # This is called by our custom buildout-generated sitecustomize.py | 170 | # This is called by our custom buildout-generated sitecustomize.py |
1656 | 141 | # in parts/scripts/sitecustomize.py. The instance name is sent to | 171 | # in parts/scripts/sitecustomize.py. The instance name is sent to |
1657 | @@ -161,3 +191,4 @@ | |||
1658 | 161 | checker.BasicTypes[grouper] = checker._iteratorChecker | 191 | checker.BasicTypes[grouper] = checker._iteratorChecker |
1659 | 162 | silence_warnings() | 192 | silence_warnings() |
1660 | 163 | customize_logger() | 193 | customize_logger() |
1661 | 194 | customize_get_converter() |