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